기본 세팅 완료
This commit is contained in:
parent
19d34716a0
commit
3b26b6ab17
|
|
@ -509,6 +509,7 @@ GameObject:
|
|||
m_Component:
|
||||
- component: {fileID: 986602854}
|
||||
- component: {fileID: 986602853}
|
||||
- component: {fileID: 986602855}
|
||||
m_Layer: 0
|
||||
m_Name: System
|
||||
m_TagString: Untagged
|
||||
|
|
@ -543,6 +544,19 @@ Transform:
|
|||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &986602855
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 986602852}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7045adda9eaa390428236252aa2f9307, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
baseUrl: https://furlib.pandoli365.com/
|
||||
--- !u!1 &1137523886
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
using UnityEngine;
|
||||
using BestHTTP;
|
||||
using System;
|
||||
|
||||
|
||||
public class NetworkManager : SingletonMonoBehaviour<NetworkManager>
|
||||
{
|
||||
[SerializeField] private string baseUrl;
|
||||
|
||||
/// <summary>
|
||||
/// 데이터를 송수신해야할 때 사용합니다. send()는 생성을 마치고 자동으로 보냅니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">역직렬화할 타입</typeparam>
|
||||
/// <param name="API">api 주소</param>
|
||||
/// <param name="onRequestFinished">데이터 받은 후 처리할 절차</param>
|
||||
/// <param name="serializeData">전송할 데이터, null로 넣으면 안보냅니다.</param>
|
||||
/// <param name="methods">동작방식</param>
|
||||
/// <param name="codec">암복호화 할 것인지</param>
|
||||
/// <returns>역직렬화된 데이터 or 기본값(반환 받지 못함)</returns>
|
||||
public HTTPRequest CreateRequest<T>(string api, object serializeData, Action<T> onSuccess, Action<T>onFail = null, HTTPMethods methods = HTTPMethods.Post)
|
||||
{
|
||||
string url = $"{baseUrl}{api}".Replace("\\p{Z}", String.Empty);
|
||||
|
||||
var httpRquest = new HTTPRequest(new Uri(url) //url 넣기
|
||||
, methods //동작 방식 넣기
|
||||
, (request, response) => {
|
||||
if (response == null || (response.StatusCode != 200))
|
||||
{
|
||||
onFail?.Invoke(default(T));
|
||||
}
|
||||
else
|
||||
{
|
||||
T deSerialized = JsonUtility.FromJson<T>(response.DataAsText);
|
||||
onSuccess?.Invoke(deSerialized);
|
||||
}
|
||||
});
|
||||
|
||||
if (serializeData != null)
|
||||
{
|
||||
httpRquest.SetHeader("Content-Type", "application/json; charset=UTF-8");
|
||||
string json = JsonUtility.ToJson(serializeData);
|
||||
httpRquest.RawData = System.Text.Encoding.UTF8.GetBytes(json);
|
||||
}
|
||||
|
||||
httpRquest.Send();
|
||||
return httpRquest;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,22 @@ public class Test : MonoBehaviour
|
|||
{
|
||||
public TMP_Text text;
|
||||
public Canvas canvas;
|
||||
|
||||
ApiResponse apiResponse;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
NetworkManager.Instance.CreateRequest<ApiResponse>("api/test",new object(),(data) => {
|
||||
apiResponse = data;
|
||||
});
|
||||
}
|
||||
void Update()
|
||||
{
|
||||
text.text = $"x : {canvas.transform.position.x}, y : {canvas.transform.position.y}";
|
||||
text.text = $"x : {canvas.transform.position.x}, y : {canvas.transform.position.y}\nip : {apiResponse.test}";
|
||||
}
|
||||
public class ApiResponse
|
||||
{
|
||||
public string test;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "BestHTTP",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
The latest documentation can be read online here: https://benedicht.github.io/BestHTTP-Documentation/
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.besthttp.proxy;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.URI;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class ProxyFinder {
|
||||
|
||||
public static String FindFor(String uriStr) {
|
||||
try {
|
||||
System.setProperty("java.net.useSystemProxies","true");
|
||||
|
||||
List<Proxy> proxies = ProxySelector.getDefault().select(new URI(uriStr));
|
||||
|
||||
for (Iterator<Proxy> iter = proxies.iterator(); iter.hasNext(); ) {
|
||||
Proxy proxy = iter.next();
|
||||
|
||||
InetSocketAddress addr = (InetSocketAddress)proxy.address();
|
||||
|
||||
if(addr == null) {
|
||||
// no op
|
||||
} else {
|
||||
return String.format("%s://%s:%s", proxy.type(), addr.getHostName(), addr.getPort());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
var Lib_BEST_HTTP_WebGL_ES_Bridge =
|
||||
{
|
||||
$es: {
|
||||
eventSourceInstances: {},
|
||||
nextInstanceId : 1,
|
||||
|
||||
Set : function(event) {
|
||||
es.eventSourceInstances[es.nextInstanceId] = event;
|
||||
return es.nextInstanceId++;
|
||||
},
|
||||
|
||||
Get : function(id) {
|
||||
return es.eventSourceInstances[id];
|
||||
},
|
||||
|
||||
Remove: function(id) {
|
||||
delete es.eventSourceInstances[id];
|
||||
},
|
||||
|
||||
_callOnError: function(errCallback, id, reason)
|
||||
{
|
||||
if (reason)
|
||||
{
|
||||
var length = lengthBytesUTF8(reason) + 1;
|
||||
var buffer = _malloc(length);
|
||||
|
||||
stringToUTF8Array(reason, HEAPU8, buffer, length);
|
||||
|
||||
Module['dynCall_vii'](errCallback, id, buffer);
|
||||
|
||||
_free(buffer);
|
||||
}
|
||||
else
|
||||
Module['dynCall_vii'](errCallback, [id, 0]);
|
||||
},
|
||||
|
||||
_GenericEventHandler: function(id, eventName, e, onMessage) {
|
||||
function AllocString(str) {
|
||||
if (str != undefined)
|
||||
{
|
||||
var length = lengthBytesUTF8(str) + 1;
|
||||
var buff = _malloc(length);
|
||||
|
||||
stringToUTF8Array(str, HEAPU8, buff, length);
|
||||
|
||||
return buff;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
var eventBuffer = AllocString(eventName);
|
||||
var dataBuffer = AllocString(e.data);
|
||||
var idBuffer = AllocString(e.id);
|
||||
|
||||
Module['dynCall_viiiii'](onMessage, id, eventBuffer, dataBuffer, idBuffer, e.retry);
|
||||
|
||||
if (eventBuffer != 0)
|
||||
_free(eventBuffer);
|
||||
|
||||
if (dataBuffer != 0)
|
||||
_free(dataBuffer);
|
||||
|
||||
if (idBuffer != 0)
|
||||
_free(idBuffer);
|
||||
}
|
||||
},
|
||||
|
||||
ES_IsSupported: function() {
|
||||
return typeof(EventSource) !== "undefined";
|
||||
},
|
||||
|
||||
ES_Create: function(urlPtr, withCredentials, onOpen, onMessage, onError)
|
||||
{
|
||||
var url = new URL(UTF8ToString(urlPtr)); ///*encodeURI*/(UTF8ToString(urlPtr)).replace(/\+/g, '%2B').replace(/%252[fF]/ig, '%2F');
|
||||
|
||||
var event = {
|
||||
onError: onError
|
||||
};
|
||||
|
||||
var id = es.nextInstanceId;
|
||||
|
||||
console.log(id + ' ES_Create(' + url + ', ' + withCredentials + ')');
|
||||
|
||||
event.eventImpl = new EventSource(url, { withCredentials: withCredentials != 0 ? true : false } );
|
||||
event.onMessage = onMessage;
|
||||
|
||||
event.eventImpl.onopen = function() {
|
||||
console.log(id + ' ES_Create - onOpen');
|
||||
|
||||
Module['dynCall_vi'](onOpen, id);
|
||||
};
|
||||
|
||||
event.eventImpl.onmessage = function(e) {
|
||||
console.log(id + ' on Generic Message');
|
||||
es._GenericEventHandler(id, undefined, e, onMessage);
|
||||
};
|
||||
|
||||
event.eventImpl.onerror = function(e) {
|
||||
console.log(id + ' ES_Create - onError');
|
||||
|
||||
es._callOnError(onError, id, "Unknown Error!");
|
||||
|
||||
if (e.target.readyState === 0)
|
||||
event.eventImpl.close();
|
||||
};
|
||||
|
||||
return es.Set(event);
|
||||
},
|
||||
|
||||
ES_AddEventHandler: function(id, eventNamePtr) {
|
||||
var eventName = UTF8ToString(eventNamePtr);
|
||||
|
||||
console.log(id + ' ES_AddEventHandler(' + eventName + ')');
|
||||
|
||||
var event = es.Get(id);
|
||||
|
||||
try
|
||||
{
|
||||
event.eventImpl.addEventListener(eventName, function(e) {
|
||||
console.log(id + ' onEvent('+ eventName + ')');
|
||||
|
||||
es._GenericEventHandler(id, eventName, e, event.onMessage);
|
||||
});
|
||||
}
|
||||
catch(e) {
|
||||
es._callOnError(event.eventImpl.onError, id, ' ' + e.name + ': ' + e.message);
|
||||
}
|
||||
},
|
||||
|
||||
ES_Close: function(id)
|
||||
{
|
||||
console.log(id + ' ES_Close');
|
||||
|
||||
var event = es.Get(id);
|
||||
|
||||
try
|
||||
{
|
||||
event.eventImpl.close();
|
||||
}
|
||||
catch(e) {
|
||||
es._callOnError(event.eventImpl.onError, id, ' ' + e.name + ': ' + e.message);
|
||||
}
|
||||
},
|
||||
|
||||
ES_Release: function(id)
|
||||
{
|
||||
console.log(id + ' ES_Release');
|
||||
|
||||
es.Remove(id);
|
||||
}
|
||||
};
|
||||
|
||||
autoAddDeps(Lib_BEST_HTTP_WebGL_ES_Bridge, '$es');
|
||||
mergeInto(LibraryManager.library, Lib_BEST_HTTP_WebGL_ES_Bridge);
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
var Lib_BEST_HTTP_WebGL_HTTP_Bridge =
|
||||
{
|
||||
/*LogLevels: {
|
||||
All: 0,
|
||||
Information: 1,
|
||||
Warning: 2,
|
||||
Error: 3,
|
||||
Exception: 4,
|
||||
None: 5
|
||||
}*/
|
||||
|
||||
$_best_http_request_bridge_global: {
|
||||
requestInstances: {},
|
||||
nextRequestId: 1,
|
||||
loglevel: 2
|
||||
},
|
||||
|
||||
XHR_Create: function(method, url, user, passwd, withCredentials)
|
||||
{
|
||||
var _url = new URL(UTF8ToString(url)); ///*encodeURI*/(UTF8ToString(url)).replace(/\+/g, '%2B').replace(/%252[fF]/ig, '%2F');
|
||||
var _method = UTF8ToString(method);
|
||||
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(_best_http_request_bridge_global.nextRequestId + ' XHR_Create - withCredentials: ' + withCredentials + ' method: ' + _method + ' url: ' + _url.toString());
|
||||
|
||||
var http = new XMLHttpRequest();
|
||||
|
||||
if (user && passwd)
|
||||
{
|
||||
var u = UTF8ToString(user);
|
||||
var p = UTF8ToString(passwd);
|
||||
|
||||
http.withCredentials = true;
|
||||
http.open(_method, _url.toString(), /*async:*/ true , u, p);
|
||||
}
|
||||
else {
|
||||
http.withCredentials = withCredentials;
|
||||
http.open(_method, _url.toString(), /*async:*/ true);
|
||||
}
|
||||
|
||||
http.responseType = 'arraybuffer';
|
||||
|
||||
_best_http_request_bridge_global.requestInstances[_best_http_request_bridge_global.nextRequestId] = http;
|
||||
return _best_http_request_bridge_global.nextRequestId++;
|
||||
},
|
||||
|
||||
XHR_SetTimeout: function (request, timeout)
|
||||
{
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(request + ' XHR_SetTimeout ' + timeout);
|
||||
|
||||
_best_http_request_bridge_global.requestInstances[request].timeout = timeout;
|
||||
},
|
||||
|
||||
XHR_SetRequestHeader: function (request, header, value)
|
||||
{
|
||||
var _header = UTF8ToString(header);
|
||||
var _value = UTF8ToString(value);
|
||||
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(request + ' XHR_SetRequestHeader ' + _header + ' ' + _value);
|
||||
|
||||
if (_header != 'Cookie')
|
||||
_best_http_request_bridge_global.requestInstances[request].setRequestHeader(_header, _value);
|
||||
else {
|
||||
var cookies = _value.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
document.cookie = cookies[i];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
XHR_CopyResponseTo: function (request, array, size) {
|
||||
var http = _best_http_request_bridge_global.requestInstances[request];
|
||||
|
||||
var response = 0;
|
||||
if (!!http.response)
|
||||
response = http.response;
|
||||
|
||||
var responseBytes = new Uint8Array(response);
|
||||
var buffer = HEAPU8.subarray(array, array + size);
|
||||
buffer.set(responseBytes)
|
||||
},
|
||||
|
||||
XHR_SetResponseHandler: function (request, onresponse, onerror, ontimeout, onaborted)
|
||||
{
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(request + ' XHR_SetResponseHandler');
|
||||
|
||||
var http = _best_http_request_bridge_global.requestInstances[request];
|
||||
// LOAD
|
||||
http.onload = function http_onload(e) {
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(request + ' - onload ' + http.status + ' ' + http.statusText);
|
||||
|
||||
if (onresponse)
|
||||
{
|
||||
var responseLength = 0;
|
||||
if (!!http.response)
|
||||
responseLength = http.response.byteLength;
|
||||
|
||||
Module['dynCall_viiiii'](onresponse, request, http.status, 0, responseLength, 0);
|
||||
}
|
||||
};
|
||||
|
||||
if (onerror)
|
||||
{
|
||||
http.onerror = function http_onerror(e) {
|
||||
function HandleError(err)
|
||||
{
|
||||
var length = lengthBytesUTF8(err) + 1;
|
||||
var buffer = _malloc(length);
|
||||
|
||||
stringToUTF8Array(err, HEAPU8, buffer, length);
|
||||
|
||||
Module['dynCall_vii'](onerror, request, buffer);
|
||||
|
||||
_free(buffer);
|
||||
}
|
||||
|
||||
if (e.error)
|
||||
HandleError(e.error);
|
||||
else
|
||||
HandleError("Unknown Error! Maybe a CORS porblem?");
|
||||
};
|
||||
}
|
||||
|
||||
if (ontimeout)
|
||||
http.ontimeout = function http_onerror(e) {
|
||||
Module['dynCall_vi'](ontimeout, request);
|
||||
};
|
||||
|
||||
if (onaborted)
|
||||
http.onabort = function http_onerror(e) {
|
||||
Module['dynCall_vi'](onaborted, request);
|
||||
};
|
||||
},
|
||||
|
||||
XHR_SetProgressHandler: function (request, onprogress, onuploadprogress)
|
||||
{
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(request + ' XHR_SetProgressHandler');
|
||||
|
||||
var http = _best_http_request_bridge_global.requestInstances[request];
|
||||
if (http)
|
||||
{
|
||||
if (onprogress)
|
||||
http.onprogress = function http_onprogress(e) {
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(request + ' XHR_SetProgressHandler - onProgress ' + e.loaded + ' ' + e.total);
|
||||
|
||||
if (e.lengthComputable)
|
||||
Module['dynCall_viii'](onprogress, request, e.loaded, e.total);
|
||||
};
|
||||
|
||||
if (onuploadprogress)
|
||||
http.upload.addEventListener("progress", function http_onprogress(e) {
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(request + ' XHR_SetProgressHandler - onUploadProgress ' + e.loaded + ' ' + e.total);
|
||||
|
||||
if (e.lengthComputable)
|
||||
Module['dynCall_viii'](onuploadprogress, request, e.loaded, e.total);
|
||||
}, true);
|
||||
}
|
||||
},
|
||||
|
||||
XHR_Send: function (request, ptr, length)
|
||||
{
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(request + ' XHR_Send ' + ptr + ' ' + length);
|
||||
|
||||
var http = _best_http_request_bridge_global.requestInstances[request];
|
||||
|
||||
try {
|
||||
if (length > 0)
|
||||
http.send(HEAPU8.subarray(ptr, ptr+length));
|
||||
else
|
||||
http.send();
|
||||
}
|
||||
catch(e) {
|
||||
if (_best_http_request_bridge_global.loglevel <= 4) /*exception*/
|
||||
console.error(request + ' ' + e.name + ": " + e.message);
|
||||
}
|
||||
},
|
||||
|
||||
XHR_GetResponseHeaders: function(request, callback)
|
||||
{
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(request + ' XHR_GetResponseHeaders');
|
||||
|
||||
var headers = ''
|
||||
var cookies = document.cookie.split(';');
|
||||
for(var i = 0; i < cookies.length; ++i) {
|
||||
const cookie = cookies[i].trim();
|
||||
|
||||
if (cookie.length > 0)
|
||||
headers += "Set-Cookie:" + cookie + "\r\n";
|
||||
}
|
||||
|
||||
var additionalHeaders = _best_http_request_bridge_global.requestInstances[request].getAllResponseHeaders().trim();
|
||||
if (additionalHeaders.length > 0) {
|
||||
headers += additionalHeaders;
|
||||
headers += "\r\n";
|
||||
}
|
||||
|
||||
headers += "\r\n";
|
||||
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(' "' + headers + '"');
|
||||
|
||||
var byteArray = new Uint8Array(headers.length);
|
||||
for(var i=0,j=headers.length;i<j;++i){
|
||||
byteArray[i]=headers.charCodeAt(i);
|
||||
}
|
||||
|
||||
var buffer = _malloc(byteArray.length);
|
||||
HEAPU8.set(byteArray, buffer);
|
||||
|
||||
Module['dynCall_viii'](callback, request, buffer, byteArray.length);
|
||||
|
||||
_free(buffer);
|
||||
},
|
||||
|
||||
XHR_GetStatusLine: function(request, callback)
|
||||
{
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(request + ' XHR_GetStatusLine');
|
||||
|
||||
var status = "HTTP/1.1 " + _best_http_request_bridge_global.requestInstances[request].status + " " + _best_http_request_bridge_global.requestInstances[request].statusText + "\r\n";
|
||||
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(status);
|
||||
|
||||
var byteArray = new Uint8Array(status.length);
|
||||
for(var i=0,j=status.length;i<j;++i){
|
||||
byteArray[i]=status.charCodeAt(i);
|
||||
}
|
||||
var buffer = _malloc(byteArray.length);
|
||||
HEAPU8.set(byteArray, buffer);
|
||||
|
||||
Module['dynCall_viii'](callback, request, buffer, byteArray.length);
|
||||
|
||||
_free(buffer);
|
||||
},
|
||||
|
||||
XHR_Abort: function (request)
|
||||
{
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(request + ' XHR_Abort');
|
||||
|
||||
_best_http_request_bridge_global.requestInstances[request].abort();
|
||||
},
|
||||
|
||||
XHR_Release: function (request)
|
||||
{
|
||||
if (_best_http_request_bridge_global.loglevel <= 1) /*information*/
|
||||
console.log(request + ' XHR_Release');
|
||||
|
||||
delete _best_http_request_bridge_global.requestInstances[request];
|
||||
},
|
||||
|
||||
XHR_SetLoglevel: function (level)
|
||||
{
|
||||
_best_http_request_bridge_global.loglevel = level;
|
||||
}
|
||||
};
|
||||
|
||||
autoAddDeps(Lib_BEST_HTTP_WebGL_HTTP_Bridge, '$_best_http_request_bridge_global');
|
||||
mergeInto(LibraryManager.library, Lib_BEST_HTTP_WebGL_HTTP_Bridge);
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
var Lib_BEST_HTTP_WebGL_WS_Bridge =
|
||||
{
|
||||
$ws: {
|
||||
webSocketInstances: {},
|
||||
nextInstanceId : 1,
|
||||
halt: false,
|
||||
/*UTF8Decoder: new TextDecoder('utf8'),*/
|
||||
|
||||
Set : function(socket) {
|
||||
ws.webSocketInstances[ws.nextInstanceId] = socket;
|
||||
return ws.nextInstanceId++;
|
||||
},
|
||||
|
||||
Get : function(id) {
|
||||
return ws.webSocketInstances[id];
|
||||
},
|
||||
|
||||
Remove: function(id) {
|
||||
delete ws.webSocketInstances[id];
|
||||
},
|
||||
|
||||
_callOnClose: function(onClose, id, code, reason)
|
||||
{
|
||||
if (ws.halt) return;
|
||||
|
||||
var length = lengthBytesUTF8(reason) + 1;
|
||||
var buffer = _malloc(length);
|
||||
|
||||
stringToUTF8Array(reason, HEAPU8, buffer, length);
|
||||
|
||||
Module['dynCall_viii'](onClose, id, code, buffer);
|
||||
|
||||
_free(buffer);
|
||||
},
|
||||
|
||||
_callOnError: function(errCallback, id, reason)
|
||||
{
|
||||
if (ws.halt) return;
|
||||
|
||||
var length = lengthBytesUTF8(reason) + 1;
|
||||
var buffer = _malloc(length);
|
||||
|
||||
stringToUTF8Array(reason, HEAPU8, buffer, length);
|
||||
|
||||
Module['dynCall_vii'](errCallback, id, buffer);
|
||||
|
||||
_free(buffer);
|
||||
}
|
||||
},
|
||||
|
||||
WS_EmergencyRelease: function() {
|
||||
console.log('WS_EmergencyRelease');
|
||||
ws.halt = true;
|
||||
},
|
||||
|
||||
WS_Create: function(url, protocol, onOpen, onText, onBinary, onError, onClose)
|
||||
{
|
||||
var urlStr = new URL(UTF8ToString(url)); ///*encodeURI*/(UTF8ToString(url)).replace(/\+/g, '%2B').replace(/%252[fF]/ig, '%2F');
|
||||
var proto = UTF8ToString(protocol);
|
||||
|
||||
console.log('WS_Create(' + urlStr + ', "' + proto + '")');
|
||||
|
||||
var socket = {
|
||||
onError: onError,
|
||||
onClose: onClose
|
||||
};
|
||||
|
||||
if (proto == '')
|
||||
socket.socketImpl = new WebSocket(urlStr);
|
||||
else
|
||||
socket.socketImpl = new WebSocket(urlStr, [proto]);
|
||||
|
||||
var id = ws.nextInstanceId;
|
||||
socket.socketImpl.binaryType = "arraybuffer";
|
||||
|
||||
socket.socketImpl.onopen = function(e) {
|
||||
console.log(id + ' WS_Create - onOpen');
|
||||
|
||||
if (ws.halt) return;
|
||||
|
||||
Module['dynCall_vi'](onOpen, id);
|
||||
};
|
||||
|
||||
socket.socketImpl.onmessage = function (e)
|
||||
{
|
||||
if (ws.halt) return;
|
||||
|
||||
// Binary?
|
||||
if (e.data instanceof ArrayBuffer)
|
||||
{
|
||||
var byteArray = new Uint8Array(e.data);
|
||||
var buffer = _malloc(byteArray.length);
|
||||
HEAPU8.set(byteArray, buffer);
|
||||
|
||||
Module['dynCall_viii'](onBinary, id, buffer, byteArray.length);
|
||||
|
||||
_free(buffer);
|
||||
}
|
||||
else // Text
|
||||
{
|
||||
var length = lengthBytesUTF8(e.data) + 1;
|
||||
var buffer = _malloc(length);
|
||||
|
||||
stringToUTF8Array(e.data, HEAPU8, buffer, length);
|
||||
|
||||
Module['dynCall_vii'](onText, id, buffer);
|
||||
|
||||
_free(buffer);
|
||||
}
|
||||
};
|
||||
|
||||
socket.socketImpl.onerror = function (e)
|
||||
{
|
||||
console.log(id + ' WS_Create - onError');
|
||||
|
||||
if (ws.halt) return;
|
||||
|
||||
// Do not call this, onClose will be called with an apropriate error code and reason
|
||||
//ws._callOnError(onError, id, "Unknown error.");
|
||||
};
|
||||
|
||||
socket.socketImpl.onclose = function (e) {
|
||||
console.log(id + ' WS_Create - onClose ' + e.code + ' ' + e.reason);
|
||||
|
||||
if (ws.halt) return;
|
||||
|
||||
//if (e.code != 1000)
|
||||
//{
|
||||
// if (e.reason != null && e.reason.length > 0)
|
||||
// ws._callOnError(onError, id, e.reason);
|
||||
// else
|
||||
// {
|
||||
// switch (e.code)
|
||||
// {
|
||||
// case 1001: ws._callOnError(onError, id, "Endpoint going away.");
|
||||
// break;
|
||||
// case 1002: ws._callOnError(onError, id, "Protocol error.");
|
||||
// break;
|
||||
// case 1003: ws._callOnError(onError, id, "Unsupported message.");
|
||||
// break;
|
||||
// case 1005: ws._callOnError(onError, id, "No status.");
|
||||
// break;
|
||||
// case 1006: ws._callOnError(onError, id, "Abnormal disconnection.");
|
||||
// break;
|
||||
// case 1009: ws._callOnError(onError, id, "Data frame too large.");
|
||||
// break;
|
||||
// default: ws._callOnError(onError, id, "Error " + e.code);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//else
|
||||
ws._callOnClose(onClose, id, e.code, e.reason);
|
||||
};
|
||||
|
||||
return ws.Set(socket);
|
||||
},
|
||||
|
||||
WS_GetState: function (id)
|
||||
{
|
||||
var socket = ws.Get(id);
|
||||
|
||||
if (typeof socket === 'undefined' ||
|
||||
socket == null ||
|
||||
typeof socket.socketImpl === 'undefined' ||
|
||||
socket.socketImpl == null)
|
||||
return 3; // closed
|
||||
|
||||
return socket.socketImpl.readyState;
|
||||
},
|
||||
|
||||
WS_GetBufferedAmount: function (id)
|
||||
{
|
||||
var socket = ws.Get(id);
|
||||
return socket.socketImpl.bufferedAmount;
|
||||
},
|
||||
|
||||
WS_Send_String: function (id, ptr, pos, length)
|
||||
{
|
||||
var socket = ws.Get(id);
|
||||
|
||||
var startPtr = ptr + pos;
|
||||
var endPtr = startPtr + length;
|
||||
|
||||
var UTF8Decoder = new TextDecoder('utf8');
|
||||
var str = UTF8Decoder.decode(HEAPU8.subarray ? HEAPU8.subarray(startPtr, endPtr) : new Uint8Array(HEAPU8.slice(startPtr, endPtr)));
|
||||
|
||||
try
|
||||
{
|
||||
socket.socketImpl.send(str);
|
||||
}
|
||||
catch(e) {
|
||||
ws._callOnError(socket.onError, id, ' ' + e.name + ': ' + e.message);
|
||||
}
|
||||
|
||||
return socket.socketImpl.bufferedAmount;
|
||||
},
|
||||
|
||||
WS_Send_Binary: function(id, ptr, pos, length)
|
||||
{
|
||||
var socket = ws.Get(id);
|
||||
|
||||
try
|
||||
{
|
||||
var buff = HEAPU8.subarray(ptr + pos, ptr + pos + length);
|
||||
socket.socketImpl.send(buff /*HEAPU8.buffer.slice(ptr + pos, ptr + pos + length)*/);
|
||||
}
|
||||
catch(e) {
|
||||
ws._callOnError(socket.onError, id, ' ' + e.name + ': ' + e.message);
|
||||
}
|
||||
|
||||
return socket.socketImpl.bufferedAmount;
|
||||
},
|
||||
|
||||
WS_Close: function (id, code, reason)
|
||||
{
|
||||
var socket = ws.Get(id);
|
||||
var reasonStr = UTF8ToString(reason);
|
||||
|
||||
console.log(id + ' WS_Close(' + code + ', ' + reasonStr + ')');
|
||||
|
||||
socket.socketImpl.close(/*ulong*/code, reasonStr);
|
||||
},
|
||||
|
||||
WS_Release: function(id)
|
||||
{
|
||||
console.log(id + ' WS_Release');
|
||||
|
||||
ws.Remove(id);
|
||||
}
|
||||
};
|
||||
|
||||
autoAddDeps(Lib_BEST_HTTP_WebGL_WS_Bridge, '$ws');
|
||||
mergeInto(LibraryManager.library, Lib_BEST_HTTP_WebGL_WS_Bridge);
|
||||
|
|
@ -0,0 +1 @@
|
|||
Release notes can be read online here: https://benedicht.github.io/BestHTTP-Documentation/#changelog/
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
namespace BestHTTP.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Authentication types that supported by BestHTTP.
|
||||
/// The authentication is defined by the server, so the Basic and Digest are not interchangeable. If you don't know what to use, the preferred way is to choose Unknow.
|
||||
/// </summary>
|
||||
public enum AuthenticationTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// If the authentication type is not known this will do a challenge turn to receive what methode should be choosen.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// The most basic authentication type. It's easy to do, and easy to crack. ;)
|
||||
/// </summary>
|
||||
Basic,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Digest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hold all information that required to authenticate to a remote server.
|
||||
/// </summary>
|
||||
public sealed class Credentials
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the Authentication. If you don't know what to use, the preferred way is to choose Unknow.
|
||||
/// </summary>
|
||||
public AuthenticationTypes Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The username to authenticate on the remote server.
|
||||
/// </summary>
|
||||
public string UserName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The password to use in the authentication process. The password will be stored only in this class.
|
||||
/// </summary>
|
||||
public string Password { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set up the authentication credentials with the username and password. The Type will be set to Unknown.
|
||||
/// </summary>
|
||||
public Credentials(string userName, string password)
|
||||
:this(AuthenticationTypes.Unknown, userName, password)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set up the authentication credentials with the given authentication type, username and password.
|
||||
/// </summary>
|
||||
public Credentials(AuthenticationTypes type, string userName, string password)
|
||||
{
|
||||
this.Type = type;
|
||||
this.UserName = userName;
|
||||
this.Password = password;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BestHTTP.Authentication
|
||||
{
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Internal class that stores all information that received from a server in a WWW-Authenticate and need to construct a valid Authorization header. Based on rfc 2617 (http://tools.ietf.org/html/rfc2617).
|
||||
/// Used only internally by the plugin.
|
||||
/// </summary>
|
||||
public sealed class Digest
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The Uri that this Digest is bound to.
|
||||
/// </summary>
|
||||
public Uri Uri { get; private set; }
|
||||
|
||||
public AuthenticationTypes Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A string to be displayed to users so they know which username and password to use.
|
||||
/// This string should contain at least the name of the host performing the authentication and might additionally indicate the collection of users who might have access.
|
||||
/// </summary>
|
||||
public string Realm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A flag, indicating that the previous request from the client was rejected because the nonce value was stale.
|
||||
/// If stale is TRUE (case-insensitive), the client may wish to simply retry the request with a new encrypted response, without the user for a new username and password.
|
||||
/// The server should only set stale to TRUE if it receives a request for which the nonce is invalid but with a valid digest for that nonce
|
||||
/// (indicating that the client knows the correct username/password).
|
||||
/// If stale is FALSE, or anything other than TRUE, or the stale directive is not present, the username and/or password are invalid, and new values must be obtained.
|
||||
/// </summary>
|
||||
public bool Stale { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Properties
|
||||
|
||||
/// <summary>
|
||||
/// A server-specified data string which should be uniquely generated each time a 401 response is made.
|
||||
/// Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
|
||||
/// </summary>
|
||||
private string Nonce { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A string of data, specified by the server, which should be returned by the client unchanged in the Authorization header of subsequent requests with URIs in the same protection space.
|
||||
/// It is recommended that this string be base64 or data.
|
||||
/// </summary>
|
||||
private string Opaque { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A string indicating a pair of algorithms used to produce the digest and a checksum. If this is not present it is assumed to be "MD5".
|
||||
/// If the algorithm is not understood, the challenge should be ignored (and a different one used, if there is more than one).
|
||||
/// </summary>
|
||||
private string Algorithm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of URIs, as specified in RFC XURI, that define the protection space.
|
||||
/// If a URI is an abs_path, it is relative to the canonical root URL (see section 1.2 above) of the server being accessed.
|
||||
/// An absoluteURI in this list may refer to a different server than the one being accessed.
|
||||
/// The client can use this list to determine the set of URIs for which the same authentication information may be sent:
|
||||
/// any URI that has a URI in this list as a prefix (after both have been made absolute) may be assumed to be in the same protection space.
|
||||
/// If this directive is omitted or its value is empty, the client should assume that the protection space consists of all URIs on the responding server.
|
||||
/// </summary>
|
||||
public List<string> ProtectedUris { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If present, it is a quoted string of one or more tokens indicating the "quality of protection" values supported by the server.
|
||||
/// The value "auth" indicates authentication. The value "auth-int" indicates authentication with integrity protection.
|
||||
/// </summary>
|
||||
private string QualityOfProtections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// his MUST be specified if a qop directive is sent (see above), and MUST NOT be specified if the server did not send a qop directive in the WWW-Authenticate header field.
|
||||
/// The nc-value is the hexadecimal count of the number of requests (including the current request) that the client has sent with the nonce value in this request.
|
||||
/// </summary>
|
||||
private int NonceCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to store the last HA1 that can be used in the next header generation when Algorithm is set to "md5-sess".
|
||||
/// </summary>
|
||||
private string HA1Sess { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
internal Digest(Uri uri)
|
||||
{
|
||||
this.Uri = uri;
|
||||
this.Algorithm = "md5";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a WWW-Authenticate header's value to retrive all information.
|
||||
/// </summary>
|
||||
public void ParseChallange(string header)
|
||||
{
|
||||
// Reset some values to its defaults.
|
||||
this.Type = AuthenticationTypes.Unknown;
|
||||
this.Stale = false;
|
||||
this.Opaque = null;
|
||||
this.HA1Sess = null;
|
||||
this.NonceCount = 0;
|
||||
this.QualityOfProtections = null;
|
||||
|
||||
if (this.ProtectedUris != null)
|
||||
this.ProtectedUris.Clear();
|
||||
|
||||
// Parse the header
|
||||
WWWAuthenticateHeaderParser qpl = new WWWAuthenticateHeaderParser(header);
|
||||
|
||||
// Then process
|
||||
foreach (var qp in qpl.Values)
|
||||
switch (qp.Key)
|
||||
{
|
||||
case "basic": this.Type = AuthenticationTypes.Basic; break;
|
||||
case "digest": this.Type = AuthenticationTypes.Digest; break;
|
||||
case "realm": this.Realm = qp.Value; break;
|
||||
case "domain":
|
||||
{
|
||||
if (string.IsNullOrEmpty(qp.Value) || qp.Value.Length == 0)
|
||||
break;
|
||||
|
||||
if (this.ProtectedUris == null)
|
||||
this.ProtectedUris = new List<string>();
|
||||
|
||||
int idx = 0;
|
||||
string val = qp.Value.Read(ref idx, ' ');
|
||||
do
|
||||
{
|
||||
this.ProtectedUris.Add(val);
|
||||
val = qp.Value.Read(ref idx, ' ');
|
||||
} while (idx < qp.Value.Length);
|
||||
|
||||
break;
|
||||
}
|
||||
case "nonce": this.Nonce = qp.Value; break;
|
||||
case "qop": this.QualityOfProtections = qp.Value; break;
|
||||
case "stale": this.Stale = bool.Parse(qp.Value); break;
|
||||
case "opaque": this.Opaque = qp.Value; break;
|
||||
case "algorithm": this.Algorithm = qp.Value; break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a string that can be set to an Authorization header.
|
||||
/// </summary>
|
||||
public string GenerateResponseHeader(HTTPRequest request, Credentials credentials, bool isProxy = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case AuthenticationTypes.Basic:
|
||||
return string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", credentials.UserName, credentials.Password))));
|
||||
|
||||
case AuthenticationTypes.Digest:
|
||||
{
|
||||
NonceCount++;
|
||||
|
||||
string HA1 = string.Empty;
|
||||
|
||||
// The cnonce-value is an opaque quoted string value provided by the client and used by both client and server to avoid chosen plaintext attacks, to provide mutual
|
||||
// authentication, and to provide some message integrity protection.
|
||||
string cnonce = new System.Random(request.GetHashCode()).Next(int.MinValue, int.MaxValue).ToString("X8");
|
||||
|
||||
string ncvalue = NonceCount.ToString("X8");
|
||||
switch (Algorithm.TrimAndLower())
|
||||
{
|
||||
case "md5":
|
||||
HA1 = string.Format("{0}:{1}:{2}", credentials.UserName, Realm, credentials.Password).CalculateMD5Hash();
|
||||
break;
|
||||
|
||||
case "md5-sess":
|
||||
if (string.IsNullOrEmpty(this.HA1Sess))
|
||||
this.HA1Sess = string.Format("{0}:{1}:{2}:{3}:{4}", credentials.UserName, Realm, credentials.Password, Nonce, ncvalue).CalculateMD5Hash();
|
||||
HA1 = this.HA1Sess;
|
||||
break;
|
||||
|
||||
default: //throw new NotSupportedException("Not supported hash algorithm found in Web Authentication: " + Algorithm);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// A string of 32 hex digits, which proves that the user knows a password. Set according to the qop value.
|
||||
string response = string.Empty;
|
||||
|
||||
// The server sent QoP-value can be a list of supported methodes(if sent at all - in this case it's null).
|
||||
// The rfc is not specify that this is a space or comma separeted list. So it can be "auth, auth-int" or "auth auth-int".
|
||||
// We will first check the longer value("auth-int") then the short one ("auth"). If one matches we will reset the qop to the exact value.
|
||||
string qop = this.QualityOfProtections != null ? this.QualityOfProtections.TrimAndLower() : null;
|
||||
|
||||
// When we authenticate with a proxy and we want to tunnel the request, we have to use the CONNECT method instead of the
|
||||
// request's, as the proxy will not know about the request itself.
|
||||
string method = isProxy ? "CONNECT" : request.MethodType.ToString().ToUpper();
|
||||
|
||||
// When we authenticate with a proxy and we want to tunnel the request, the uri must match what we are sending in the CONNECT request's
|
||||
// Host header.
|
||||
string uri = isProxy ? request.CurrentUri.Host + ":" + request.CurrentUri.Port : request.CurrentUri.GetRequestPathAndQueryURL();
|
||||
|
||||
if (qop == null)
|
||||
{
|
||||
string HA2 = string.Concat(request.MethodType.ToString().ToUpper(), ":", request.CurrentUri.GetRequestPathAndQueryURL()).CalculateMD5Hash();
|
||||
response = string.Format("{0}:{1}:{2}", HA1, Nonce, HA2).CalculateMD5Hash();
|
||||
}
|
||||
else if (qop.Contains("auth-int"))
|
||||
{
|
||||
qop = "auth-int";
|
||||
|
||||
byte[] entityBody = request.GetEntityBody();
|
||||
|
||||
if (entityBody == null)
|
||||
entityBody = BufferPool.NoData; //string.Empty.GetASCIIBytes();
|
||||
|
||||
string HA2 = string.Format("{0}:{1}:{2}", method, uri, new BufferSegment(entityBody, 0, entityBody.Length).CalculateMD5Hash()).CalculateMD5Hash();
|
||||
|
||||
response = string.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, Nonce, ncvalue, cnonce, qop, HA2).CalculateMD5Hash();
|
||||
}
|
||||
else if (qop.Contains("auth"))
|
||||
{
|
||||
qop = "auth";
|
||||
string HA2 = string.Concat(method, ":", uri).CalculateMD5Hash();
|
||||
|
||||
response = string.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, Nonce, ncvalue, cnonce, qop, HA2).CalculateMD5Hash();
|
||||
}
|
||||
else //throw new NotSupportedException("Unrecognized Quality of Protection value found: " + this.QualityOfProtections);
|
||||
return string.Empty;
|
||||
|
||||
string result = string.Format("Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", cnonce=\"{4}\", response=\"{5}\"",
|
||||
credentials.UserName, Realm, Nonce, uri, cnonce, response);
|
||||
|
||||
if (qop != null)
|
||||
result += String.Concat(", qop=\"", qop, "\", nc=", ncvalue);
|
||||
|
||||
if (!string.IsNullOrEmpty(Opaque))
|
||||
result = String.Concat(result, ", opaque=\"", Opaque, "\"");
|
||||
|
||||
return result;
|
||||
}// end of case "digest":
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public bool IsUriProtected(Uri uri)
|
||||
{
|
||||
// http://tools.ietf.org/html/rfc2617#section-3.2.1
|
||||
// An absoluteURI in this list may refer to
|
||||
// a different server than the one being accessed. The client can use
|
||||
// this list to determine the set of URIs for which the same
|
||||
// authentication information may be sent: any URI that has a URI in
|
||||
// this list as a prefix (after both have been made absolute) may be
|
||||
// assumed to be in the same protection space. If this directive is
|
||||
// omitted or its value is empty, the client should assume that the
|
||||
// protection space consists of all URIs on the responding server.
|
||||
|
||||
if (string.CompareOrdinal(uri.Host, this.Uri.Host) != 0)
|
||||
return false;
|
||||
|
||||
string uriStr = uri.ToString();
|
||||
|
||||
if (ProtectedUris != null && ProtectedUris.Count > 0)
|
||||
for (int i = 0; i < ProtectedUris.Count; ++i)
|
||||
if (uriStr.Contains(ProtectedUris[i]))
|
||||
return true;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BestHTTP.PlatformSupport.Threading;
|
||||
|
||||
namespace BestHTTP.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores and manages already received digest infos.
|
||||
/// </summary>
|
||||
public static class DigestStore
|
||||
{
|
||||
private static Dictionary<string, Digest> Digests = new Dictionary<string, Digest>();
|
||||
|
||||
private static System.Threading.ReaderWriterLockSlim rwLock = new System.Threading.ReaderWriterLockSlim(System.Threading.LockRecursionPolicy.NoRecursion);
|
||||
|
||||
/// <summary>
|
||||
/// Array of algorithms that the plugin supports. It's in the order of priority(first has the highest priority).
|
||||
/// </summary>
|
||||
private static string[] SupportedAlgorithms = new string[] { "digest", "basic" };
|
||||
|
||||
public static Digest Get(Uri uri)
|
||||
{
|
||||
using (new ReadLock(rwLock))
|
||||
{
|
||||
Digest digest = null;
|
||||
if (Digests.TryGetValue(uri.Host, out digest))
|
||||
if (!digest.IsUriProtected(uri))
|
||||
return null;
|
||||
return digest;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// It will retrieve or create a new Digest for the given Uri.
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
/// <returns></returns>
|
||||
public static Digest GetOrCreate(Uri uri)
|
||||
{
|
||||
using (new WriteLock(rwLock))
|
||||
{
|
||||
Digest digest = null;
|
||||
if (!Digests.TryGetValue(uri.Host, out digest))
|
||||
Digests.Add(uri.Host, digest = new Digest(uri));
|
||||
return digest;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Remove(Uri uri)
|
||||
{
|
||||
using (new WriteLock(rwLock))
|
||||
Digests.Remove(uri.Host);
|
||||
}
|
||||
|
||||
public static string FindBest(List<string> authHeaders)
|
||||
{
|
||||
if (authHeaders == null || authHeaders.Count == 0)
|
||||
return string.Empty;
|
||||
|
||||
List<string> headers = new List<string>(authHeaders.Count);
|
||||
for (int i = 0; i < authHeaders.Count; ++i)
|
||||
headers.Add(authHeaders[i].ToLowerInvariant());
|
||||
|
||||
for (int i = 0; i < SupportedAlgorithms.Length; ++i)
|
||||
{
|
||||
int idx = headers.FindIndex((header) => header.StartsWith(SupportedAlgorithms[i]));
|
||||
if (idx != -1)
|
||||
return authHeaders[idx];
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,501 @@
|
|||
#if !BESTHTTP_DISABLE_CACHING
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace BestHTTP.Caching
|
||||
{
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.PlatformSupport.FileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Holds all metadata that need for efficient caching, so we don't need to touch the disk to load headers.
|
||||
/// </summary>
|
||||
public class HTTPCacheFileInfo : IComparable<HTTPCacheFileInfo>
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The uri that this HTTPCacheFileInfo belongs to.
|
||||
/// </summary>
|
||||
public Uri Uri { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The last access time to this cache entity. The date is in UTC.
|
||||
/// </summary>
|
||||
public DateTime LastAccess { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of the cache entity's body.
|
||||
/// </summary>
|
||||
public int BodyLength { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// ETag of the entity.
|
||||
/// </summary>
|
||||
public string ETag { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// LastModified date of the entity.
|
||||
/// </summary>
|
||||
public string LastModified { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the cache will expire.
|
||||
/// </summary>
|
||||
public DateTime Expires { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The age that came with the response
|
||||
/// </summary>
|
||||
public long Age { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum how long the entry should served from the cache without revalidation.
|
||||
/// </summary>
|
||||
public long MaxAge { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Date that came with the response.
|
||||
/// </summary>
|
||||
public DateTime Date { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the entity must be revalidated with the server or can be serverd directly from the cache without touching the server when the content is considered stale.
|
||||
/// </summary>
|
||||
public bool MustRevalidate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If it's true, the client always have to revalidate the cached content when it's stale.
|
||||
/// </summary>
|
||||
public bool NoCache { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// It's a grace period to serve staled content without revalidation.
|
||||
/// </summary>
|
||||
public long StaleWhileRevalidate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Allows the client to serve stale content if the server responds with an 5xx error.
|
||||
/// </summary>
|
||||
public long StaleIfError { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date and time when the HTTPResponse received.
|
||||
/// </summary>
|
||||
public DateTime Received { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cached path.
|
||||
/// </summary>
|
||||
public string ConstructedPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is the index of the entity. Filenames are generated from this value.
|
||||
/// </summary>
|
||||
internal UInt64 MappedNameIDX { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
internal HTTPCacheFileInfo(Uri uri)
|
||||
:this(uri, DateTime.UtcNow, -1)
|
||||
{
|
||||
}
|
||||
|
||||
internal HTTPCacheFileInfo(Uri uri, DateTime lastAcces, int bodyLength)
|
||||
{
|
||||
this.Uri = uri;
|
||||
this.LastAccess = lastAcces;
|
||||
this.BodyLength = bodyLength;
|
||||
this.MaxAge = -1;
|
||||
|
||||
this.MappedNameIDX = HTTPCacheService.GetNameIdx();
|
||||
}
|
||||
|
||||
internal HTTPCacheFileInfo(Uri uri, System.IO.BinaryReader reader, int version)
|
||||
{
|
||||
this.Uri = uri;
|
||||
this.LastAccess = DateTime.FromBinary(reader.ReadInt64());
|
||||
this.BodyLength = reader.ReadInt32();
|
||||
|
||||
switch(version)
|
||||
{
|
||||
case 3:
|
||||
this.NoCache = reader.ReadBoolean();
|
||||
this.StaleWhileRevalidate = reader.ReadInt64();
|
||||
this.StaleIfError = reader.ReadInt64();
|
||||
goto case 2;
|
||||
|
||||
case 2:
|
||||
this.MappedNameIDX = reader.ReadUInt64();
|
||||
goto case 1;
|
||||
|
||||
case 1:
|
||||
{
|
||||
this.ETag = reader.ReadString();
|
||||
this.LastModified = reader.ReadString();
|
||||
this.Expires = DateTime.FromBinary(reader.ReadInt64());
|
||||
this.Age = reader.ReadInt64();
|
||||
this.MaxAge = reader.ReadInt64();
|
||||
this.Date = DateTime.FromBinary(reader.ReadInt64());
|
||||
this.MustRevalidate = reader.ReadBoolean();
|
||||
this.Received = DateTime.FromBinary(reader.ReadInt64());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Functions
|
||||
|
||||
internal void SaveTo(System.IO.BinaryWriter writer)
|
||||
{
|
||||
// base
|
||||
writer.Write(this.LastAccess.ToBinary());
|
||||
writer.Write(this.BodyLength);
|
||||
|
||||
// version 3
|
||||
writer.Write(this.NoCache);
|
||||
writer.Write(this.StaleWhileRevalidate);
|
||||
writer.Write(this.StaleIfError);
|
||||
|
||||
// version 2
|
||||
writer.Write(this.MappedNameIDX);
|
||||
|
||||
// version 1
|
||||
writer.Write(this.ETag);
|
||||
writer.Write(this.LastModified);
|
||||
writer.Write(this.Expires.ToBinary());
|
||||
writer.Write(this.Age);
|
||||
writer.Write(this.MaxAge);
|
||||
writer.Write(this.Date.ToBinary());
|
||||
writer.Write(this.MustRevalidate);
|
||||
writer.Write(this.Received.ToBinary());
|
||||
}
|
||||
|
||||
public string GetPath()
|
||||
{
|
||||
if (ConstructedPath != null)
|
||||
return ConstructedPath;
|
||||
|
||||
return ConstructedPath = System.IO.Path.Combine(HTTPCacheService.CacheFolder, MappedNameIDX.ToString("X"));
|
||||
}
|
||||
|
||||
public bool IsExists()
|
||||
{
|
||||
if (!HTTPCacheService.IsSupported)
|
||||
return false;
|
||||
|
||||
return HTTPManager.IOService.FileExists(GetPath());
|
||||
}
|
||||
|
||||
internal void Delete()
|
||||
{
|
||||
if (!HTTPCacheService.IsSupported)
|
||||
return;
|
||||
|
||||
string path = GetPath();
|
||||
try
|
||||
{
|
||||
HTTPManager.IOService.FileDelete(path);
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
finally
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
// MappedNameIDX will remain the same. When we re-save an entity, it will not reset the MappedNameIDX.
|
||||
this.BodyLength = -1;
|
||||
this.ETag = string.Empty;
|
||||
this.Expires = DateTime.FromBinary(0);
|
||||
this.LastModified = string.Empty;
|
||||
this.Age = 0;
|
||||
this.MaxAge = -1;
|
||||
this.Date = DateTime.FromBinary(0);
|
||||
this.MustRevalidate = false;
|
||||
this.Received = DateTime.FromBinary(0);
|
||||
this.NoCache = false;
|
||||
this.StaleWhileRevalidate = 0;
|
||||
this.StaleIfError = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Caching
|
||||
|
||||
internal void SetUpCachingValues(HTTPResponse response)
|
||||
{
|
||||
response.CacheFileInfo = this;
|
||||
|
||||
this.ETag = response.GetFirstHeaderValue("ETag").ToStr(this.ETag ?? string.Empty);
|
||||
this.Expires = response.GetFirstHeaderValue("Expires").ToDateTime(this.Expires);
|
||||
this.LastModified = response.GetFirstHeaderValue("Last-Modified").ToStr(this.LastModified ?? string.Empty);
|
||||
|
||||
this.Age = response.GetFirstHeaderValue("Age").ToInt64(this.Age);
|
||||
|
||||
this.Date = response.GetFirstHeaderValue("Date").ToDateTime(this.Date);
|
||||
|
||||
List<string> cacheControls = response.GetHeaderValues("cache-control");
|
||||
if (cacheControls != null && cacheControls.Count > 0)
|
||||
{
|
||||
// Merge all Cache-Control header values into one
|
||||
string cacheControl = cacheControls[0];
|
||||
for (int i = 1; i < cacheControls.Count; ++i)
|
||||
cacheControl += "," + cacheControls[i];
|
||||
|
||||
if (!string.IsNullOrEmpty(cacheControl))
|
||||
{
|
||||
HeaderParser parser = new HeaderParser(cacheControl);
|
||||
|
||||
if (parser.Values != null)
|
||||
{
|
||||
for (int i = 0; i < parser.Values.Count; ++i)
|
||||
{
|
||||
var kvp = parser.Values[i];
|
||||
|
||||
switch(kvp.Key.ToLowerInvariant())
|
||||
{
|
||||
case "max-age":
|
||||
if (kvp.HasValue)
|
||||
{
|
||||
// Some cache proxies will return float values
|
||||
double maxAge;
|
||||
if (double.TryParse(kvp.Value, out maxAge))
|
||||
this.MaxAge = (int)maxAge;
|
||||
else
|
||||
this.MaxAge = 0;
|
||||
}
|
||||
else
|
||||
this.MaxAge = 0;
|
||||
break;
|
||||
|
||||
case "stale-while-revalidate":
|
||||
this.StaleWhileRevalidate = kvp.HasValue ? kvp.Value.ToInt64(0) : 0;
|
||||
break;
|
||||
|
||||
case "stale-if-error":
|
||||
this.StaleIfError = kvp.HasValue ? kvp.Value.ToInt64(0) : 0;
|
||||
break;
|
||||
|
||||
case "must-revalidate":
|
||||
this.MustRevalidate = true;
|
||||
break;
|
||||
|
||||
case "no-cache":
|
||||
this.NoCache = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//string[] options = cacheControl.ToLowerInvariant().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
//
|
||||
//string[] kvp = options.FindOption("max-age");
|
||||
//if (kvp != null && kvp.Length > 1)
|
||||
//{
|
||||
// // Some cache proxies will return float values
|
||||
// double maxAge;
|
||||
// if (double.TryParse(kvp[1], out maxAge))
|
||||
// this.MaxAge = (int)maxAge;
|
||||
// else
|
||||
// this.MaxAge = 0;
|
||||
//}
|
||||
//else
|
||||
// this.MaxAge = 0;
|
||||
//
|
||||
//kvp = options.FindOption("stale-while-revalidate");
|
||||
//if (kvp != null && kvp.Length == 2 && !string.IsNullOrEmpty(kvp[1]))
|
||||
// this.StaleWhileRevalidate = kvp[1].ToInt64(0);
|
||||
//
|
||||
//kvp = options.FindOption("stale-if-error");
|
||||
//if (kvp != null && kvp.Length == 2 && !string.IsNullOrEmpty(kvp[1]))
|
||||
// this.StaleIfError = kvp[1].ToInt64(0);
|
||||
//
|
||||
//this.MustRevalidate = cacheControl.Contains("must-revalidate");
|
||||
//this.NoCache = cacheControl.Contains("no-cache");
|
||||
}
|
||||
}
|
||||
|
||||
this.Received = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// isInError should be true if downloading the content fails, and in that case, it might extend the content's freshness
|
||||
/// </summary>
|
||||
public bool WillExpireInTheFuture(bool isInError)
|
||||
{
|
||||
if (!IsExists())
|
||||
return false;
|
||||
|
||||
// https://csswizardry.com/2019/03/cache-control-for-civilians/#no-cache
|
||||
// no-cache will always hit the network as it has to revalidate with the server before it can release the browser’s cached copy (unless the server responds with a fresher response),
|
||||
// but if the server responds favourably, the network transfer is only a file’s headers: the body can be grabbed from cache rather than redownloaded.
|
||||
if (this.NoCache)
|
||||
return false;
|
||||
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.4 :
|
||||
// The max-age directive takes priority over Expires
|
||||
if (MaxAge > 0)
|
||||
{
|
||||
// Age calculation:
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.3
|
||||
|
||||
long apparent_age = Math.Max(0, (long)(this.Received - this.Date).TotalSeconds);
|
||||
long corrected_received_age = Math.Max(apparent_age, this.Age);
|
||||
long resident_time = (long)(DateTime.UtcNow - this.Date).TotalSeconds;
|
||||
long current_age = corrected_received_age + resident_time;
|
||||
|
||||
long maxAge = this.MaxAge + (this.NoCache ? 0 : this.StaleWhileRevalidate) + (isInError ? this.StaleIfError : 0);
|
||||
|
||||
return current_age < maxAge || this.Expires > DateTime.UtcNow;
|
||||
}
|
||||
|
||||
return this.Expires > DateTime.UtcNow;
|
||||
}
|
||||
|
||||
internal void SetUpRevalidationHeaders(HTTPRequest request)
|
||||
{
|
||||
if (!IsExists())
|
||||
return;
|
||||
|
||||
// -If an entity tag has been provided by the origin server, MUST use that entity tag in any cache-conditional request (using If-Match or If-None-Match).
|
||||
// -If only a Last-Modified value has been provided by the origin server, SHOULD use that value in non-subrange cache-conditional requests (using If-Modified-Since).
|
||||
// -If both an entity tag and a Last-Modified value have been provided by the origin server, SHOULD use both validators in cache-conditional requests. This allows both HTTP/1.0 and HTTP/1.1 caches to respond appropriately.
|
||||
|
||||
if (!string.IsNullOrEmpty(ETag))
|
||||
request.SetHeader("If-None-Match", ETag);
|
||||
|
||||
if (!string.IsNullOrEmpty(LastModified))
|
||||
request.SetHeader("If-Modified-Since", LastModified);
|
||||
}
|
||||
|
||||
public System.IO.Stream GetBodyStream(out int length)
|
||||
{
|
||||
if (!IsExists())
|
||||
{
|
||||
length = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
length = BodyLength;
|
||||
|
||||
LastAccess = DateTime.UtcNow;
|
||||
|
||||
Stream stream = HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.OpenRead);
|
||||
stream.Seek(-length, System.IO.SeekOrigin.End);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
internal void ReadResponseTo(HTTPRequest request)
|
||||
{
|
||||
if (!IsExists())
|
||||
return;
|
||||
|
||||
LastAccess = DateTime.UtcNow;
|
||||
|
||||
using (Stream stream = HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.OpenRead))
|
||||
{
|
||||
request.Response = new HTTPResponse(request, stream, request.UseStreaming, true);
|
||||
request.Response.CacheFileInfo = this;
|
||||
request.Response.Receive(BodyLength);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Store(HTTPResponse response)
|
||||
{
|
||||
if (!HTTPCacheService.IsSupported)
|
||||
return;
|
||||
|
||||
string path = GetPath();
|
||||
|
||||
// Path name too long, we don't want to get exceptions
|
||||
if (path.Length > HTTPManager.MaxPathLength)
|
||||
return;
|
||||
|
||||
if (HTTPManager.IOService.FileExists(path))
|
||||
Delete();
|
||||
|
||||
using (Stream writer = HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.Create))
|
||||
{
|
||||
writer.WriteLine("HTTP/{0}.{1} {2} {3}", response.VersionMajor, response.VersionMinor, response.StatusCode, response.Message);
|
||||
foreach (var kvp in response.Headers)
|
||||
{
|
||||
for (int i = 0; i < kvp.Value.Count; ++i)
|
||||
writer.WriteLine("{0}: {1}", kvp.Key, kvp.Value[i]);
|
||||
}
|
||||
|
||||
writer.WriteLine();
|
||||
|
||||
writer.Write(response.Data, 0, response.Data.Length);
|
||||
}
|
||||
|
||||
BodyLength = response.Data.Length;
|
||||
|
||||
LastAccess = DateTime.UtcNow;
|
||||
|
||||
SetUpCachingValues(response);
|
||||
}
|
||||
|
||||
internal System.IO.Stream GetSaveStream(HTTPResponse response)
|
||||
{
|
||||
if (!HTTPCacheService.IsSupported)
|
||||
return null;
|
||||
|
||||
LastAccess = DateTime.UtcNow;
|
||||
|
||||
string path = GetPath();
|
||||
|
||||
if (HTTPManager.IOService.FileExists(path))
|
||||
Delete();
|
||||
|
||||
// Path name too long, we don't want to get exceptions
|
||||
if (path.Length > HTTPManager.MaxPathLength)
|
||||
return null;
|
||||
|
||||
// First write out the headers
|
||||
using (Stream writer = HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.Create))
|
||||
{
|
||||
writer.WriteLine("HTTP/1.1 {0} {1}", response.StatusCode, response.Message);
|
||||
foreach (var kvp in response.Headers)
|
||||
{
|
||||
for (int i = 0; i < kvp.Value.Count; ++i)
|
||||
writer.WriteLine("{0}: {1}", kvp.Key, kvp.Value[i]);
|
||||
}
|
||||
|
||||
writer.WriteLine();
|
||||
}
|
||||
|
||||
// If caching is enabled and the response is from cache, and no content-length header set, then we set one to the response.
|
||||
if (response.IsFromCache && !response.HasHeader("content-length"))
|
||||
response.AddHeader("content-length", BodyLength.ToString());
|
||||
|
||||
SetUpCachingValues(response);
|
||||
|
||||
// then create the stream with Append FileMode
|
||||
return HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.Append);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IComparable<HTTPCacheFileInfo>
|
||||
|
||||
public int CompareTo(HTTPCacheFileInfo other)
|
||||
{
|
||||
return this.LastAccess.CompareTo(other.LastAccess);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#if !BESTHTTP_DISABLE_CACHING
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BestHTTP.Caching
|
||||
{
|
||||
//static class HTTPCacheFileLock
|
||||
//{
|
||||
// private static Dictionary<Uri, object> FileLocks = new Dictionary<Uri, object>();
|
||||
// //private static object SyncRoot = new object();
|
||||
// private static System.Threading.ReaderWriterLockSlim rwLock = new System.Threading.ReaderWriterLockSlim(System.Threading.LockRecursionPolicy.NoRecursion);
|
||||
//
|
||||
// internal static object Acquire(Uri uri)
|
||||
// {
|
||||
// rwLock.EnterUpgradeableReadLock();
|
||||
// try
|
||||
// {
|
||||
// object fileLock;
|
||||
// if (!FileLocks.TryGetValue(uri, out fileLock))
|
||||
// {
|
||||
// rwLock.EnterWriteLock();
|
||||
// try
|
||||
// {
|
||||
// FileLocks.Add(uri, fileLock = new object());
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// rwLock.ExitWriteLock();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return fileLock;
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// rwLock.ExitUpgradeableReadLock();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#if !BESTHTTP_DISABLE_CACHING
|
||||
|
||||
using System;
|
||||
|
||||
namespace BestHTTP.Caching
|
||||
{
|
||||
public sealed class HTTPCacheMaintananceParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Delete cache entries that accessed older then this value. If TimeSpan.FromSeconds(0) is used then all cache entries will be deleted. With TimeSpan.FromDays(2) entries that older then two days will be deleted.
|
||||
/// </summary>
|
||||
public TimeSpan DeleteOlder { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the cache is larger then the MaxCacheSize after the first maintanance step, then the maintanance job will forcedelete cache entries starting with the oldest last accessed one.
|
||||
/// </summary>
|
||||
public ulong MaxCacheSize { get; private set; }
|
||||
|
||||
public HTTPCacheMaintananceParams(TimeSpan deleteOlder, ulong maxCacheSize)
|
||||
{
|
||||
this.DeleteOlder = deleteOlder;
|
||||
this.MaxCacheSize = maxCacheSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,838 @@
|
|||
#if !BESTHTTP_DISABLE_CACHING
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
//
|
||||
// Version 1: Initial release
|
||||
// Version 2: Filenames are generated from an index.
|
||||
//
|
||||
|
||||
namespace BestHTTP.Caching
|
||||
{
|
||||
using BestHTTP.Core;
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.PlatformSupport.FileSystem;
|
||||
using BestHTTP.PlatformSupport.Threading;
|
||||
|
||||
public sealed class UriComparer : IEqualityComparer<Uri>
|
||||
{
|
||||
public bool Equals(Uri x, Uri y)
|
||||
{
|
||||
return Uri.Compare(x, y, UriComponents.HttpRequestUrl, UriFormat.SafeUnescaped, StringComparison.Ordinal) == 0;
|
||||
}
|
||||
|
||||
public int GetHashCode(Uri uri)
|
||||
{
|
||||
return uri.ToString().GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class HTTPCacheService
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
/// <summary>
|
||||
/// Library file-format versioning support
|
||||
/// </summary>
|
||||
private const int LibraryVersion = 3;
|
||||
|
||||
public static bool IsSupported
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsSupportCheckDone)
|
||||
return isSupported;
|
||||
|
||||
try
|
||||
{
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
// Explicitly disable cahing under WebGL
|
||||
isSupported = false;
|
||||
#else
|
||||
// If DirectoryExists throws an exception we will set IsSupprted to false
|
||||
HTTPManager.IOService.DirectoryExists(HTTPManager.GetRootCacheFolder());
|
||||
isSupported = true;
|
||||
#endif
|
||||
}
|
||||
catch
|
||||
{
|
||||
isSupported = false;
|
||||
|
||||
HTTPManager.Logger.Warning("HTTPCacheService", "Cache Service Disabled!");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsSupportCheckDone = true;
|
||||
}
|
||||
|
||||
return isSupported;
|
||||
}
|
||||
}
|
||||
private static bool isSupported;
|
||||
private static bool IsSupportCheckDone;
|
||||
|
||||
private static Dictionary<Uri, HTTPCacheFileInfo> library;
|
||||
|
||||
private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
||||
|
||||
private static Dictionary<UInt64, HTTPCacheFileInfo> UsedIndexes = new Dictionary<ulong, HTTPCacheFileInfo>();
|
||||
|
||||
internal static string CacheFolder { get; private set; }
|
||||
private static string LibraryPath { get; set; }
|
||||
|
||||
private volatile static bool InClearThread;
|
||||
private volatile static bool InMaintainenceThread;
|
||||
|
||||
/// <summary>
|
||||
/// This property returns true while the service is in a Clear or Maintenance thread.
|
||||
/// </summary>
|
||||
public static bool IsDoingMaintainence { get { return InClearThread || InMaintainenceThread; } }
|
||||
|
||||
/// <summary>
|
||||
/// Stores the index of the next stored entity. The entity's file name is generated from this index.
|
||||
/// </summary>
|
||||
private static UInt64 NextNameIDX;
|
||||
|
||||
#endregion
|
||||
|
||||
static HTTPCacheService()
|
||||
{
|
||||
NextNameIDX = 0x0001;
|
||||
}
|
||||
|
||||
#region Common Functions
|
||||
|
||||
internal static void CheckSetup()
|
||||
{
|
||||
if (!HTTPCacheService.IsSupported)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
SetupCacheFolder();
|
||||
LoadLibrary();
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
|
||||
internal static void SetupCacheFolder()
|
||||
{
|
||||
if (!HTTPCacheService.IsSupported)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(CacheFolder) || string.IsNullOrEmpty(LibraryPath))
|
||||
{
|
||||
CacheFolder = System.IO.Path.Combine(HTTPManager.GetRootCacheFolder(), "HTTPCache");
|
||||
if (!HTTPManager.IOService.DirectoryExists(CacheFolder))
|
||||
HTTPManager.IOService.DirectoryCreate(CacheFolder);
|
||||
|
||||
LibraryPath = System.IO.Path.Combine(HTTPManager.GetRootCacheFolder(), "Library");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
isSupported = false;
|
||||
|
||||
HTTPManager.Logger.Warning("HTTPCacheService", "Cache Service Disabled!");
|
||||
}
|
||||
}
|
||||
|
||||
internal static UInt64 GetNameIdx()
|
||||
{
|
||||
UInt64 result = NextNameIDX;
|
||||
|
||||
do
|
||||
{
|
||||
NextNameIDX = ++NextNameIDX % UInt64.MaxValue;
|
||||
} while (UsedIndexes.ContainsKey(NextNameIDX));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool HasEntity(Uri uri)
|
||||
{
|
||||
if (!IsSupported)
|
||||
return false;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
using (new ReadLock(rwLock))
|
||||
return library.ContainsKey(uri);
|
||||
}
|
||||
|
||||
public static bool DeleteEntity(Uri uri, bool removeFromLibrary = true)
|
||||
{
|
||||
if (!IsSupported)
|
||||
return false;
|
||||
|
||||
// 2019.05.10: Removed all locking except the one on the library.
|
||||
|
||||
CheckSetup();
|
||||
|
||||
using (new WriteLock(rwLock))
|
||||
{
|
||||
DeleteEntityImpl(uri, removeFromLibrary, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteEntityImpl(Uri uri, bool removeFromLibrary = true, bool useLocking = false)
|
||||
{
|
||||
HTTPCacheFileInfo info;
|
||||
bool inStats = library.TryGetValue(uri, out info);
|
||||
if (inStats)
|
||||
info.Delete();
|
||||
|
||||
if (inStats && removeFromLibrary)
|
||||
{
|
||||
if (useLocking)
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
library.Remove(uri);
|
||||
UsedIndexes.Remove(info.MappedNameIDX);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (useLocking)
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
|
||||
}
|
||||
|
||||
internal static bool IsCachedEntityExpiresInTheFuture(HTTPRequest request)
|
||||
{
|
||||
if (!IsSupported || request.DisableCache)
|
||||
return false;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
HTTPCacheFileInfo info = null;
|
||||
|
||||
using (new ReadLock(rwLock))
|
||||
{
|
||||
if (!library.TryGetValue(request.CurrentUri, out info))
|
||||
return false;
|
||||
}
|
||||
|
||||
return info.WillExpireInTheFuture(request.State == HTTPRequestStates.ConnectionTimedOut ||
|
||||
request.State == HTTPRequestStates.TimedOut ||
|
||||
request.State == HTTPRequestStates.Error ||
|
||||
(request.State == HTTPRequestStates.Finished && request.Response != null && request.Response.StatusCode >= 500));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility function to set the cache control headers according to the spec.: http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.4
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
internal static void SetHeaders(HTTPRequest request)
|
||||
{
|
||||
if (!IsSupported)
|
||||
return;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
request.RemoveHeader("If-None-Match");
|
||||
request.RemoveHeader("If-Modified-Since");
|
||||
|
||||
HTTPCacheFileInfo info = null;
|
||||
|
||||
using (new ReadLock(rwLock))
|
||||
{
|
||||
if (!library.TryGetValue(request.CurrentUri, out info))
|
||||
return;
|
||||
}
|
||||
|
||||
info.SetUpRevalidationHeaders(request);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Get Functions
|
||||
|
||||
public static HTTPCacheFileInfo GetEntity(Uri uri)
|
||||
{
|
||||
if (!IsSupported)
|
||||
return null;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
HTTPCacheFileInfo info = null;
|
||||
|
||||
using (new ReadLock(rwLock))
|
||||
library.TryGetValue(uri, out info);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
internal static void GetFullResponse(HTTPRequest request)
|
||||
{
|
||||
if (!IsSupported)
|
||||
return;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
HTTPCacheFileInfo info = null;
|
||||
|
||||
using (new ReadLock(rwLock))
|
||||
{
|
||||
if (!library.TryGetValue(request.CurrentUri, out info))
|
||||
return;
|
||||
|
||||
info.ReadResponseTo(request);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Storing
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given response can be cached. http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4
|
||||
/// </summary>
|
||||
/// <returns>Returns true if cacheable, false otherwise.</returns>
|
||||
internal static bool IsCacheble(Uri uri, HTTPMethods method, HTTPResponse response)
|
||||
{
|
||||
if (!IsSupported)
|
||||
return false;
|
||||
|
||||
if (method != HTTPMethods.Get)
|
||||
return false;
|
||||
|
||||
if (response == null)
|
||||
return false;
|
||||
|
||||
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.12 - Cache Replacement
|
||||
// It MAY insert it into cache storage and MAY, if it meets all other requirements, use it to respond to any future requests that would previously have caused the old response to be returned.
|
||||
//if (response.StatusCode == 304)
|
||||
// return false;
|
||||
|
||||
// Partial response
|
||||
if (response.StatusCode == 206)
|
||||
return false;
|
||||
|
||||
if (response.StatusCode < 200 || response.StatusCode >= 400)
|
||||
return false;
|
||||
|
||||
//http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.2
|
||||
bool hasValidMaxAge = false;
|
||||
var cacheControls = response.GetHeaderValues("cache-control");
|
||||
if (cacheControls != null)
|
||||
{
|
||||
if (cacheControls.Exists(headerValue =>
|
||||
{
|
||||
HeaderParser parser = new HeaderParser(headerValue);
|
||||
if (parser.Values != null && parser.Values.Count > 0) {
|
||||
for (int i = 0; i < parser.Values.Count; ++i)
|
||||
{
|
||||
var value = parser.Values[i];
|
||||
|
||||
// https://csswizardry.com/2019/03/cache-control-for-civilians/#no-store
|
||||
if (value.Key == "no-store")
|
||||
return true;
|
||||
|
||||
if (value.Key == "max-age" && value.HasValue)
|
||||
{
|
||||
double maxAge;
|
||||
if (double.TryParse(value.Value, out maxAge))
|
||||
{
|
||||
// A negative max-age value is a no cache
|
||||
if (maxAge <= 0)
|
||||
return true;
|
||||
hasValidMaxAge = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}))
|
||||
return false;
|
||||
}
|
||||
|
||||
var pragmas = response.GetHeaderValues("pragma");
|
||||
if (pragmas != null)
|
||||
{
|
||||
if (pragmas.Exists(headerValue =>
|
||||
{
|
||||
string value = headerValue.ToLowerInvariant();
|
||||
return value.Contains("no-store") || value.Contains("no-cache");
|
||||
}))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Responses with byte ranges not supported yet.
|
||||
var byteRanges = response.GetHeaderValues("content-range");
|
||||
if (byteRanges != null)
|
||||
return false;
|
||||
|
||||
// Store only if at least one caching header with proper value present
|
||||
|
||||
var etag = response.GetFirstHeaderValue("ETag");
|
||||
if (!string.IsNullOrEmpty(etag))
|
||||
return true;
|
||||
|
||||
var expires = response.GetFirstHeaderValue("Expires").ToDateTime(DateTime.FromBinary(0));
|
||||
if (expires >= DateTime.UtcNow)
|
||||
return true;
|
||||
|
||||
if (response.GetFirstHeaderValue("Last-Modified") != null)
|
||||
return true;
|
||||
|
||||
return hasValidMaxAge;
|
||||
}
|
||||
|
||||
internal static HTTPCacheFileInfo Store(Uri uri, HTTPMethods method, HTTPResponse response)
|
||||
{
|
||||
if (response == null || response.Data == null || response.Data.Length == 0)
|
||||
return null;
|
||||
|
||||
if (!IsSupported)
|
||||
return null;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
HTTPCacheFileInfo info = null;
|
||||
|
||||
using (new WriteLock(rwLock))
|
||||
{
|
||||
if (!library.TryGetValue(uri, out info))
|
||||
{
|
||||
library.Add(uri, info = new HTTPCacheFileInfo(uri));
|
||||
UsedIndexes.Add(info.MappedNameIDX, info);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
info.Store(response);
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Verbose("HTTPCacheService", string.Format("{0} - Saved to cache", uri.ToString()), response.baseRequest.Context);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If something happens while we write out the response, than we will delete it because it might be in an invalid state.
|
||||
DeleteEntityImpl(uri);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
internal static void SetUpCachingValues(Uri uri, HTTPResponse response)
|
||||
{
|
||||
if (!IsSupported)
|
||||
return;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
using (new WriteLock(rwLock))
|
||||
{
|
||||
HTTPCacheFileInfo info = null;
|
||||
if (!library.TryGetValue(uri, out info))
|
||||
{
|
||||
library.Add(uri, info = new HTTPCacheFileInfo(uri));
|
||||
UsedIndexes.Add(info.MappedNameIDX, info);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
info.SetUpCachingValues(response);
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Verbose("HTTPCacheService", string.Format("{0} - SetUpCachingValues done!", uri.ToString()), response.baseRequest.Context);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If something happens while we write out the response, than we will delete it because it might be in an invalid state.
|
||||
DeleteEntityImpl(uri);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static System.IO.Stream PrepareStreamed(Uri uri, HTTPResponse response)
|
||||
{
|
||||
if (!IsSupported)
|
||||
return null;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
HTTPCacheFileInfo info;
|
||||
|
||||
using (new WriteLock(rwLock))
|
||||
{
|
||||
if (!library.TryGetValue(uri, out info))
|
||||
{
|
||||
library.Add(uri, info = new HTTPCacheFileInfo(uri));
|
||||
UsedIndexes.Add(info.MappedNameIDX, info);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return info.GetSaveStream(response);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If something happens while we write out the response, than we will delete it because it might be in an invalid state.
|
||||
DeleteEntityImpl(uri, true, true);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Maintenance Functions
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all cache entity. Non blocking.
|
||||
/// <remarks>Call it only if there no requests currently processed, because cache entries can be deleted while a server sends back a 304 result, so there will be no data to read from the cache!</remarks>
|
||||
/// </summary>
|
||||
public static void BeginClear()
|
||||
{
|
||||
if (!IsSupported)
|
||||
return;
|
||||
|
||||
if (InClearThread)
|
||||
return;
|
||||
InClearThread = true;
|
||||
|
||||
SetupCacheFolder();
|
||||
|
||||
PlatformSupport.Threading.ThreadedRunner.RunShortLiving(ClearImpl);
|
||||
}
|
||||
|
||||
private static void ClearImpl()
|
||||
{
|
||||
if (!IsSupported)
|
||||
return;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
using (new WriteLock(rwLock))
|
||||
{
|
||||
try
|
||||
{
|
||||
// GetFiles will return a string array that contains the files in the folder with the full path
|
||||
string[] cacheEntries = HTTPManager.IOService.GetFiles(CacheFolder);
|
||||
|
||||
if (cacheEntries != null)
|
||||
for (int i = 0; i < cacheEntries.Length; ++i)
|
||||
{
|
||||
// We need a try-catch block because between the Directory.GetFiles call and the File.Delete calls a maintenance job, or other file operations can delete any file from the cache folder.
|
||||
// So while there might be some problem with any file, we don't want to abort the whole for loop
|
||||
try
|
||||
{
|
||||
HTTPManager.IOService.FileDelete(cacheEntries[i]);
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
UsedIndexes.Clear();
|
||||
library.Clear();
|
||||
NextNameIDX = 0x0001;
|
||||
|
||||
InClearThread = false;
|
||||
|
||||
PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all expired cache entity.
|
||||
/// <remarks>Call it only if there no requests currently processed, because cache entries can be deleted while a server sends back a 304 result, so there will be no data to read from the cache!</remarks>
|
||||
/// </summary>
|
||||
public static void BeginMaintainence(HTTPCacheMaintananceParams maintananceParam)
|
||||
{
|
||||
if (maintananceParam == null)
|
||||
throw new ArgumentNullException("maintananceParams == null");
|
||||
|
||||
if (!HTTPCacheService.IsSupported)
|
||||
return;
|
||||
|
||||
if (InMaintainenceThread)
|
||||
return;
|
||||
|
||||
InMaintainenceThread = true;
|
||||
|
||||
SetupCacheFolder();
|
||||
|
||||
PlatformSupport.Threading.ThreadedRunner.RunShortLiving(MaintananceImpl, maintananceParam);
|
||||
}
|
||||
|
||||
private static void MaintananceImpl(HTTPCacheMaintananceParams maintananceParam)
|
||||
{
|
||||
CheckSetup();
|
||||
|
||||
using (new WriteLock(rwLock))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Delete cache entries older than the given time.
|
||||
DateTime deleteOlderAccessed = DateTime.UtcNow - maintananceParam.DeleteOlder;
|
||||
List<HTTPCacheFileInfo> removedEntities = new List<HTTPCacheFileInfo>();
|
||||
foreach (var kvp in library)
|
||||
if (kvp.Value.LastAccess < deleteOlderAccessed)
|
||||
{
|
||||
DeleteEntityImpl(kvp.Key, false, false);
|
||||
removedEntities.Add(kvp.Value);
|
||||
}
|
||||
|
||||
for (int i = 0; i < removedEntities.Count; ++i)
|
||||
{
|
||||
library.Remove(removedEntities[i].Uri);
|
||||
UsedIndexes.Remove(removedEntities[i].MappedNameIDX);
|
||||
}
|
||||
removedEntities.Clear();
|
||||
|
||||
ulong cacheSize = GetCacheSizeImpl();
|
||||
|
||||
// This step will delete all entries starting with the oldest LastAccess property while the cache size greater then the MaxCacheSize in the given param.
|
||||
if (cacheSize > maintananceParam.MaxCacheSize)
|
||||
{
|
||||
List<HTTPCacheFileInfo> fileInfos = new List<HTTPCacheFileInfo>(library.Count);
|
||||
|
||||
foreach (var kvp in library)
|
||||
fileInfos.Add(kvp.Value);
|
||||
|
||||
fileInfos.Sort();
|
||||
|
||||
int idx = 0;
|
||||
while (cacheSize >= maintananceParam.MaxCacheSize && idx < fileInfos.Count)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fi = fileInfos[idx];
|
||||
ulong length = (ulong)fi.BodyLength;
|
||||
|
||||
DeleteEntityImpl(fi.Uri);
|
||||
|
||||
cacheSize -= length;
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
finally
|
||||
{
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
InMaintainenceThread = false;
|
||||
|
||||
PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetCacheEntityCount()
|
||||
{
|
||||
if (!HTTPCacheService.IsSupported)
|
||||
return 0;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
using (new ReadLock(rwLock))
|
||||
{
|
||||
return library.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public static ulong GetCacheSize()
|
||||
{
|
||||
if (!IsSupported)
|
||||
return 0;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
using (new ReadLock (rwLock))
|
||||
{
|
||||
return GetCacheSizeImpl();
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong GetCacheSizeImpl()
|
||||
{
|
||||
ulong size = 0;
|
||||
|
||||
foreach (var kvp in library)
|
||||
if (kvp.Value.BodyLength > 0)
|
||||
size += (ulong)kvp.Value.BodyLength;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cache Library Management
|
||||
|
||||
private static void LoadLibrary()
|
||||
{
|
||||
// Already loaded?
|
||||
if (library != null)
|
||||
return;
|
||||
|
||||
if (!IsSupported)
|
||||
return;
|
||||
|
||||
int version = 1;
|
||||
|
||||
using (new WriteLock(rwLock))
|
||||
{
|
||||
library = new Dictionary<Uri, HTTPCacheFileInfo>(new UriComparer());
|
||||
try
|
||||
{
|
||||
using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.OpenRead))
|
||||
using (var br = new System.IO.BinaryReader(fs))
|
||||
{
|
||||
version = br.ReadInt32();
|
||||
|
||||
if (version > 1)
|
||||
NextNameIDX = br.ReadUInt64();
|
||||
|
||||
int statCount = br.ReadInt32();
|
||||
|
||||
for (int i = 0; i < statCount; ++i)
|
||||
{
|
||||
Uri uri = new Uri(br.ReadString());
|
||||
|
||||
var entity = new HTTPCacheFileInfo(uri, br, version);
|
||||
if (entity.IsExists())
|
||||
{
|
||||
library.Add(uri, entity);
|
||||
|
||||
if (version > 1)
|
||||
UsedIndexes.Add(entity.MappedNameIDX, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Exception("HTTPCacheService", "LoadLibrary", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (version == 1)
|
||||
BeginClear();
|
||||
else
|
||||
DeleteUnusedFiles();
|
||||
}
|
||||
|
||||
internal static void SaveLibrary()
|
||||
{
|
||||
if (library == null)
|
||||
return;
|
||||
|
||||
if (!IsSupported)
|
||||
return;
|
||||
|
||||
using (new WriteLock(rwLock))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.Create))
|
||||
using (var bw = new System.IO.BinaryWriter(fs))
|
||||
{
|
||||
bw.Write(LibraryVersion);
|
||||
bw.Write(NextNameIDX);
|
||||
|
||||
bw.Write(library.Count);
|
||||
foreach (var kvp in library)
|
||||
{
|
||||
bw.Write(kvp.Key.ToString());
|
||||
|
||||
kvp.Value.SaveTo(bw);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Exception("HTTPCacheService", "SaveLibrary", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetBodyLength(Uri uri, int bodyLength)
|
||||
{
|
||||
if (!IsSupported)
|
||||
return;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
using (new WriteLock(rwLock))
|
||||
{
|
||||
HTTPCacheFileInfo fileInfo;
|
||||
if (library.TryGetValue(uri, out fileInfo))
|
||||
fileInfo.BodyLength = bodyLength;
|
||||
else
|
||||
{
|
||||
library.Add(uri, fileInfo = new HTTPCacheFileInfo(uri, DateTime.UtcNow, bodyLength));
|
||||
UsedIndexes.Add(fileInfo.MappedNameIDX, fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all files from the cache folder that isn't in the Library.
|
||||
/// </summary>
|
||||
private static void DeleteUnusedFiles()
|
||||
{
|
||||
if (!IsSupported)
|
||||
return;
|
||||
|
||||
CheckSetup();
|
||||
|
||||
// GetFiles will return a string array that contains the files in the folder with the full path
|
||||
string[] cacheEntries = HTTPManager.IOService.GetFiles(CacheFolder);
|
||||
|
||||
for (int i = 0; i < cacheEntries.Length; ++i)
|
||||
{
|
||||
// We need a try-catch block because between the Directory.GetFiles call and the File.Delete calls a maintenance job, or other file operations can delete any file from the cache folder.
|
||||
// So while there might be some problem with any file, we don't want to abort the whole for loop
|
||||
try
|
||||
{
|
||||
string filename = System.IO.Path.GetFileName(cacheEntries[i]);
|
||||
UInt64 idx = 0;
|
||||
bool deleteFile = false;
|
||||
if (UInt64.TryParse(filename, System.Globalization.NumberStyles.AllowHexSpecifier, null, out idx))
|
||||
{
|
||||
using (new ReadLock(rwLock))
|
||||
deleteFile = !UsedIndexes.ContainsKey(idx);
|
||||
}
|
||||
else
|
||||
deleteFile = true;
|
||||
|
||||
if (deleteFile)
|
||||
HTTPManager.IOService.FileDelete(cacheEntries[i]);
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,390 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
using BestHTTP.Extensions;
|
||||
|
||||
namespace BestHTTP.Connections
|
||||
{
|
||||
public sealed class BufferedReadNetworkStream : Stream
|
||||
{
|
||||
#region Network Stats
|
||||
public static long TotalNetworkBytesReceived { get => _totalNetworkBytesReceived; }
|
||||
private static long _totalNetworkBytesReceived;
|
||||
internal static void IncrementTotalNetworkBytesReceived(int amount) => System.Threading.Interlocked.Add(ref _totalNetworkBytesReceived, amount);
|
||||
|
||||
public static long TotalNetworkBytesSent { get => _totalNetworkBytesSent; }
|
||||
private static long _totalNetworkBytesSent;
|
||||
|
||||
internal static void IncrementTotalNetworkBytesSent(int amount) => System.Threading.Interlocked.Add(ref _totalNetworkBytesSent, amount);
|
||||
|
||||
public static int TotalConnections { get => _totalConnections; }
|
||||
private static int _totalConnections;
|
||||
|
||||
public static int OpenConnections { get => _openConnections; }
|
||||
private static int _openConnections;
|
||||
|
||||
internal static void IncrementCurrentConnections()
|
||||
{
|
||||
System.Threading.Interlocked.Increment(ref _totalConnections);
|
||||
System.Threading.Interlocked.Increment(ref _openConnections);
|
||||
}
|
||||
internal static void DecrementCurrentConnections() => System.Threading.Interlocked.Decrement(ref _openConnections);
|
||||
|
||||
internal static void ResetNetworkStats()
|
||||
{
|
||||
System.Threading.Interlocked.Exchange(ref _totalNetworkBytesReceived, 0);
|
||||
System.Threading.Interlocked.Exchange(ref _totalNetworkBytesSent, 0);
|
||||
System.Threading.Interlocked.Exchange(ref _totalConnections, 0);
|
||||
System.Threading.Interlocked.Exchange(ref _openConnections, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override bool CanRead { get { throw new NotImplementedException(); } }
|
||||
|
||||
public override bool CanSeek { get { throw new NotImplementedException(); } }
|
||||
|
||||
public override bool CanWrite { get { throw new NotImplementedException(); } }
|
||||
|
||||
public override long Length { get { throw new NotImplementedException(); } }
|
||||
|
||||
public override long Position { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
|
||||
|
||||
private ReadOnlyBufferedStream readStream;
|
||||
private Stream innerStream;
|
||||
|
||||
public BufferedReadNetworkStream(Stream stream, int bufferSize)
|
||||
{
|
||||
this.innerStream = stream;
|
||||
this.readStream = new ReadOnlyBufferedStream(stream, bufferSize);
|
||||
|
||||
IncrementCurrentConnections();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int read = this.readStream.Read(buffer, offset, count);
|
||||
IncrementTotalNetworkBytesReceived(read);
|
||||
return read;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
IncrementTotalNetworkBytesSent(count);
|
||||
this.innerStream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
|
||||
if (this.innerStream != null)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (this.innerStream != null)
|
||||
{
|
||||
DecrementCurrentConnections();
|
||||
|
||||
var stream = this.innerStream;
|
||||
this.innerStream = null;
|
||||
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
if (this.readStream != null)
|
||||
{
|
||||
this.readStream.Close();
|
||||
this.readStream = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Non-used experimental stream. Reading from the inner stream is done parallel and Read is blocked if no data is buffered.
|
||||
// Additionally BC reads 5 bytes for the TLS header, than the payload. Buffering data from the network could save at least one context switch per TLS message.
|
||||
// In theory it, could help as reading from the network could be done parallel with TLS decryption.
|
||||
// However, if decrypting data is done faster than data is coming on the network, waiting for data longer and letting SpinWait to go deep-sleep it's going to
|
||||
// resume the thread milliseconds after new data is available. Those little afters are adding up and actually slowing down the download.
|
||||
// Not using locking just calling TryDequeue until there's data would solve the slow-down, but with the price of using 100% CPU of a core.
|
||||
// The whole struggle might worth it if Unity would implement SocketAsyncEventArgs properly.
|
||||
//sealed class BufferedReadNetworkStream : Stream
|
||||
//{
|
||||
// public override bool CanRead => throw new NotImplementedException();
|
||||
//
|
||||
// public override bool CanSeek => throw new NotImplementedException();
|
||||
//
|
||||
// public override bool CanWrite => throw new NotImplementedException();
|
||||
//
|
||||
// public override long Length => throw new NotImplementedException();
|
||||
//
|
||||
// public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
//
|
||||
// byte[] buf;
|
||||
// int available = 0;
|
||||
// int pos = 0;
|
||||
//
|
||||
// private System.Net.Sockets.Socket client;
|
||||
// int readBufferSize;
|
||||
// int bufferSize;
|
||||
// private System.Threading.SpinWait spinWait = new System.Threading.SpinWait();
|
||||
//
|
||||
// System.Collections.Concurrent.ConcurrentQueue<BufferSegment> downloadedData = new System.Collections.Concurrent.ConcurrentQueue<BufferSegment>();
|
||||
// private int downloadedBytes;
|
||||
// private System.Threading.SpinWait downWait = new System.Threading.SpinWait();
|
||||
// private int closed = 0;
|
||||
//
|
||||
// //System.Net.Sockets.SocketAsyncEventArgs socketAsyncEventArgs = new System.Net.Sockets.SocketAsyncEventArgs();
|
||||
//
|
||||
// //DateTime started;
|
||||
//
|
||||
// public BufferedReadNetworkStream(System.Net.Sockets.Socket socket, int readBufferSize, int bufferSize)
|
||||
// {
|
||||
// this.client = socket;
|
||||
// this.readBufferSize = readBufferSize;
|
||||
// this.bufferSize = bufferSize;
|
||||
//
|
||||
// //this.socketAsyncEventArgs.AcceptSocket = this.client;
|
||||
// //
|
||||
// //var buffer = BufferPool.Get(this.readBufferSize, true);
|
||||
// //this.socketAsyncEventArgs.SetBuffer(buffer, 0, buffer.Length);
|
||||
// //
|
||||
// ////var bufferList = new List<ArraySegment<byte>>();
|
||||
// ////for (int i = 0; i < 1; i++)
|
||||
// ////{
|
||||
// //// var buffer = BufferPool.Get(this.readBufferSize, true);
|
||||
// //// bufferList.Add(new ArraySegment<byte>(buffer));
|
||||
// ////}
|
||||
// ////this.socketAsyncEventArgs.BufferList = bufferList;
|
||||
// //
|
||||
// //this.socketAsyncEventArgs.Completed += SocketAsyncEventArgs_Completed;
|
||||
// //
|
||||
// //this.started = DateTime.Now;
|
||||
// //if (!this.client.ReceiveAsync(this.socketAsyncEventArgs))
|
||||
// // SocketAsyncEventArgs_Completed(null, this.socketAsyncEventArgs);
|
||||
//
|
||||
// BestHTTP.PlatformSupport.Threading.ThreadedRunner.RunShortLiving(() =>
|
||||
// {
|
||||
// DateTime started = DateTime.Now;
|
||||
// try
|
||||
// {
|
||||
// while (closed == 0)
|
||||
// {
|
||||
// var buffer = BufferPool.Get(this.readBufferSize, true);
|
||||
//
|
||||
// int count = this.client.Receive(buffer, 0, buffer.Length, System.Net.Sockets.SocketFlags.None);
|
||||
// //int count = 0;
|
||||
// //unsafe {
|
||||
// // fixed (byte* pBuffer = buffer)
|
||||
// // {
|
||||
// // int zero = 0;
|
||||
// // count = recvfrom(this.client.Handle, pBuffer, buffer.Length, SocketFlags.None, null, ref zero);
|
||||
// // }
|
||||
// //}
|
||||
//
|
||||
// this.downloadedData.Enqueue(new BufferSegment(buffer, 0, count));
|
||||
// System.Threading.Interlocked.Add(ref downloadedBytes, count);
|
||||
//
|
||||
// if (HTTPManager.Logger.Level <= Logger.Loglevels.Warning)
|
||||
// HTTPManager.Logger.Warning(nameof(BufferedReadNetworkStream), $"read count: {count:N0} downloadedBytes: {downloadedBytes:N0} / {this.bufferSize:N0}");
|
||||
//
|
||||
// if (count <= 0)
|
||||
// {
|
||||
// System.Threading.Interlocked.Exchange(ref closed, 1);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// while (downloadedBytes >= this.bufferSize)
|
||||
// {
|
||||
// downWait.SpinOnce();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// UnityEngine.Debug.LogException(ex);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// UnityEngine.Debug.Log($"Reading finished in {(DateTime.Now - started)}");
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// //private void SocketAsyncEventArgs_Completed(object sender, System.Net.Sockets.SocketAsyncEventArgs e)
|
||||
// //{
|
||||
// // this.downloadedData.Enqueue(new BufferSegment(e.Buffer, 0, e.BytesTransferred));
|
||||
// //
|
||||
// // if (e.BytesTransferred == 0)
|
||||
// // {
|
||||
// // UnityEngine.Debug.Log($"Reading finished in {(DateTime.Now - started)}");
|
||||
// // return;
|
||||
// // }
|
||||
// //
|
||||
// // int down = System.Threading.Interlocked.Add(ref downloadedBytes, e.BytesTransferred);
|
||||
// //
|
||||
// // if (HTTPManager.Logger.Level <= Logger.Loglevels.Warning)
|
||||
// // HTTPManager.Logger.Warning(nameof(BufferedReadNetworkStream), $"SocketAsyncEventArgs_Completed - read count: {e.BytesTransferred:N0} downloadedBytes: {down:N0} / {this.bufferSize:N0}");
|
||||
// //
|
||||
// // var buffer = BufferPool.Get(this.readBufferSize, true);
|
||||
// // this.socketAsyncEventArgs.SetBuffer(buffer, 0, buffer.Length);
|
||||
// //
|
||||
// // if (!this.client.ReceiveAsync(this.socketAsyncEventArgs))
|
||||
// // SocketAsyncEventArgs_Completed(null, this.socketAsyncEventArgs);
|
||||
// //}
|
||||
//
|
||||
// private void SwitchBuffers(bool waitForData)
|
||||
// {
|
||||
// //HTTPManager.Logger.Error("Read", $"{this.downloadedData.Count}");
|
||||
// BufferSegment segment;
|
||||
// while (!this.downloadedData.TryDequeue(out segment))
|
||||
// {
|
||||
// if (waitForData && closed == 0)
|
||||
// {
|
||||
// if (HTTPManager.Logger.Level <= Logger.Loglevels.Error)
|
||||
// HTTPManager.Logger.Error(nameof(BufferedReadNetworkStream), $"SpinOnce");
|
||||
// this.spinWait.SpinOnce();
|
||||
// }
|
||||
// else
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// //if (segment.Count <= 0)
|
||||
// // throw new Exception("Connection closed!");
|
||||
//
|
||||
// if (buf != null)
|
||||
// BufferPool.Release(buf);
|
||||
//
|
||||
// System.Threading.Interlocked.Add(ref downloadedBytes, -segment.Count);
|
||||
//
|
||||
// buf = segment.Data;
|
||||
// available = segment.Count;
|
||||
// pos = 0;
|
||||
// }
|
||||
//
|
||||
// public override int Read(byte[] buffer, int offset, int size)
|
||||
// {
|
||||
// if (this.buf == null)
|
||||
// {
|
||||
// SwitchBuffers(true);
|
||||
// }
|
||||
//
|
||||
// if (size <= available)
|
||||
// {
|
||||
// Array.Copy(buf, pos, buffer, offset, size);
|
||||
// available -= size;
|
||||
// pos += size;
|
||||
//
|
||||
// if (available == 0)
|
||||
// {
|
||||
// SwitchBuffers(false);
|
||||
// }
|
||||
//
|
||||
// return size;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// int readcount = 0;
|
||||
// if (available > 0)
|
||||
// {
|
||||
// Array.Copy(buf, pos, buffer, offset, available);
|
||||
// offset += available;
|
||||
// readcount += available;
|
||||
// available = 0;
|
||||
// pos = 0;
|
||||
// }
|
||||
//
|
||||
// while (true)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// SwitchBuffers(true);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// if (readcount > 0)
|
||||
// {
|
||||
// return readcount;
|
||||
// }
|
||||
//
|
||||
// throw (ex);
|
||||
// }
|
||||
//
|
||||
// if (available < 1)
|
||||
// {
|
||||
// if (readcount > 0)
|
||||
// {
|
||||
// return readcount;
|
||||
// }
|
||||
//
|
||||
// return available;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// int toread = size - readcount;
|
||||
// if (toread <= available)
|
||||
// {
|
||||
// Array.Copy(buf, pos, buffer, offset, toread);
|
||||
// available -= toread;
|
||||
// pos += toread;
|
||||
// readcount += toread;
|
||||
// return readcount;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Array.Copy(buf, pos, buffer, offset, available);
|
||||
// offset += available;
|
||||
// readcount += available;
|
||||
// pos = 0;
|
||||
// available = 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// 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)
|
||||
// {
|
||||
// this.client.Send(buffer, offset, count, System.Net.Sockets.SocketFlags.None);
|
||||
//
|
||||
// HTTPManager.Logger.Warning(nameof(BufferedReadNetworkStream), $"Wrote: {count}");
|
||||
// }
|
||||
//
|
||||
// public override void Close()
|
||||
// {
|
||||
// base.Close();
|
||||
//
|
||||
// //socketAsyncEventArgs.Dispose();
|
||||
// //socketAsyncEventArgs = null;
|
||||
// }
|
||||
//
|
||||
// public override void Flush()
|
||||
// {
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
using System;
|
||||
using BestHTTP.Logger;
|
||||
|
||||
namespace BestHTTP.Connections
|
||||
{
|
||||
public abstract class ConnectionBase : IDisposable
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The address of the server that this connection is bound to.
|
||||
/// </summary>
|
||||
public string ServerAddress { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The state of this connection.
|
||||
/// </summary>
|
||||
public HTTPConnectionStates State { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the State is HTTPConnectionStates.Processing, then it holds a HTTPRequest instance. Otherwise it's null.
|
||||
/// </summary>
|
||||
public HTTPRequest CurrentRequest { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// How much the connection kept alive after its last request processing.
|
||||
/// </summary>
|
||||
public virtual TimeSpan KeepAliveTime { get; protected set; }
|
||||
|
||||
public virtual bool CanProcessMultiple { get { return false; } }
|
||||
|
||||
/// <summary>
|
||||
/// When we start to process the current request. It's set after the connection is established.
|
||||
/// </summary>
|
||||
public DateTime StartTime { get; protected set; }
|
||||
|
||||
public Uri LastProcessedUri { get; protected set; }
|
||||
|
||||
public DateTime LastProcessTime { get; protected set; }
|
||||
|
||||
internal LoggingContext Context;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Privates
|
||||
|
||||
private bool IsThreaded;
|
||||
|
||||
#endregion
|
||||
|
||||
public ConnectionBase(string serverAddress)
|
||||
:this(serverAddress, true)
|
||||
{}
|
||||
|
||||
public ConnectionBase(string serverAddress, bool threaded)
|
||||
{
|
||||
this.ServerAddress = serverAddress;
|
||||
this.State = HTTPConnectionStates.Initial;
|
||||
this.LastProcessTime = DateTime.Now;
|
||||
this.KeepAliveTime = HTTPManager.MaxConnectionIdleTime;
|
||||
this.IsThreaded = threaded;
|
||||
|
||||
this.Context = new LoggingContext(this);
|
||||
this.Context.Add("ServerAddress", serverAddress);
|
||||
this.Context.Add("Threaded", threaded);
|
||||
}
|
||||
|
||||
internal virtual void Process(HTTPRequest request)
|
||||
{
|
||||
if (State == HTTPConnectionStates.Processing)
|
||||
throw new Exception("Connection already processing a request! " + this.ToString());
|
||||
|
||||
StartTime = DateTime.MaxValue;
|
||||
State = HTTPConnectionStates.Processing;
|
||||
|
||||
CurrentRequest = request;
|
||||
LastProcessedUri = CurrentRequest.CurrentUri;
|
||||
|
||||
if (IsThreaded)
|
||||
PlatformSupport.Threading.ThreadedRunner.RunLongLiving(ThreadFunc);
|
||||
else
|
||||
ThreadFunc();
|
||||
}
|
||||
|
||||
protected virtual void ThreadFunc()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ShutdownTypes ShutdownType { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the plugin shuts down immediately.
|
||||
/// </summary>
|
||||
public virtual void Shutdown(ShutdownTypes type)
|
||||
{
|
||||
this.ShutdownType = type;
|
||||
}
|
||||
|
||||
#region Dispose Pattern
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
|
||||
~ConnectionBase()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0}:{1}]", this.GetHashCode(), this.ServerAddress);
|
||||
}
|
||||
|
||||
public virtual bool TestConnection() => true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,372 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BestHTTP.Authentication;
|
||||
using BestHTTP.Core;
|
||||
using BestHTTP.Extensions;
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
using BestHTTP.Caching;
|
||||
#endif
|
||||
#if !BESTHTTP_DISABLE_COOKIES
|
||||
using BestHTTP.Cookies;
|
||||
#endif
|
||||
|
||||
using BestHTTP.Logger;
|
||||
using BestHTTP.Timings;
|
||||
|
||||
namespace BestHTTP.Connections
|
||||
{
|
||||
/// <summary>
|
||||
/// https://tools.ietf.org/html/draft-thomson-hybi-http-timeout-03
|
||||
/// Test servers: http://tools.ietf.org/ http://nginx.org/
|
||||
/// </summary>
|
||||
public sealed class KeepAliveHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// A host sets the value of the "timeout" parameter to the time that the host will allow an idle connection to remain open before it is closed. A connection is idle if no data is sent or received by a host.
|
||||
/// </summary>
|
||||
public TimeSpan TimeOut { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The "max" parameter has been used to indicate the maximum number of requests that would be made on the connection.This parameter is deprecated.Any limit on requests can be enforced by sending "Connection: close" and closing the connection.
|
||||
/// </summary>
|
||||
public int MaxRequests { get; private set; }
|
||||
|
||||
public void Parse(List<string> headerValues)
|
||||
{
|
||||
HeaderParser parser = new HeaderParser(headerValues[0]);
|
||||
HeaderValue value;
|
||||
if (parser.TryGet("timeout", out value) && value.HasValue)
|
||||
{
|
||||
int intValue = 0;
|
||||
if (int.TryParse(value.Value, out intValue) && intValue > 1)
|
||||
this.TimeOut = TimeSpan.FromSeconds(intValue - 1);
|
||||
else
|
||||
this.TimeOut = TimeSpan.MaxValue;
|
||||
}
|
||||
|
||||
if (parser.TryGet("max", out value) && value.HasValue)
|
||||
{
|
||||
int intValue = 0;
|
||||
if (int.TryParse("max", out intValue))
|
||||
this.MaxRequests = intValue;
|
||||
else
|
||||
this.MaxRequests = int.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConnectionHelper
|
||||
{
|
||||
public static void HandleResponse(string context, HTTPRequest request, out bool resendRequest, out HTTPConnectionStates proposedConnectionState, ref KeepAliveHeader keepAlive, LoggingContext loggingContext1 = null, LoggingContext loggingContext2 = null, LoggingContext loggingContext3 = null)
|
||||
{
|
||||
resendRequest = false;
|
||||
proposedConnectionState = HTTPConnectionStates.Processing;
|
||||
|
||||
if (request.Response != null)
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_COOKIES
|
||||
// Try to store cookies before we do anything else, as we may remove the response deleting the cookies as well.
|
||||
if (request.IsCookiesEnabled)
|
||||
CookieJar.Set(request.Response);
|
||||
#endif
|
||||
|
||||
switch (request.Response.StatusCode)
|
||||
{
|
||||
// Not authorized
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
|
||||
case 401:
|
||||
{
|
||||
string authHeader = DigestStore.FindBest(request.Response.GetHeaderValues("www-authenticate"));
|
||||
if (!string.IsNullOrEmpty(authHeader))
|
||||
{
|
||||
var digest = DigestStore.GetOrCreate(request.CurrentUri);
|
||||
digest.ParseChallange(authHeader);
|
||||
|
||||
if (request.Credentials != null && digest.IsUriProtected(request.CurrentUri) && (!request.HasHeader("Authorization") || digest.Stale))
|
||||
resendRequest = true;
|
||||
}
|
||||
|
||||
goto default;
|
||||
}
|
||||
|
||||
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
case 407:
|
||||
{
|
||||
if (request.Proxy == null)
|
||||
goto default;
|
||||
|
||||
resendRequest = request.Proxy.SetupRequest(request);
|
||||
|
||||
goto default;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Redirected
|
||||
case 301: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2
|
||||
case 302: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.3
|
||||
case 303: // "See Other"
|
||||
case 307: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.8
|
||||
case 308: // http://tools.ietf.org/html/rfc7238
|
||||
{
|
||||
if (request.RedirectCount >= request.MaxRedirects)
|
||||
goto default;
|
||||
request.RedirectCount++;
|
||||
|
||||
string location = request.Response.GetFirstHeaderValue("location");
|
||||
if (!string.IsNullOrEmpty(location))
|
||||
{
|
||||
Uri redirectUri = ConnectionHelper.GetRedirectUri(request, location);
|
||||
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - Redirected to Location: '{1}' redirectUri: '{1}'", context, location, redirectUri), loggingContext1, loggingContext2, loggingContext3);
|
||||
|
||||
if (redirectUri == request.CurrentUri)
|
||||
{
|
||||
HTTPManager.Logger.Information("HTTPConnection", string.Format("[{0}] - Redirected to the same location!", context), loggingContext1, loggingContext2, loggingContext3);
|
||||
goto default;
|
||||
}
|
||||
|
||||
// Let the user to take some control over the redirection
|
||||
if (!request.CallOnBeforeRedirection(redirectUri))
|
||||
{
|
||||
HTTPManager.Logger.Information("HTTPConnection", string.Format("[{0}] OnBeforeRedirection returned False", context), loggingContext1, loggingContext2, loggingContext3);
|
||||
goto default;
|
||||
}
|
||||
|
||||
// Remove the previously set Host header.
|
||||
request.RemoveHeader("Host");
|
||||
|
||||
// Set the Referer header to the last Uri.
|
||||
request.SetHeader("Referer", request.CurrentUri.ToString());
|
||||
|
||||
// Set the new Uri, the CurrentUri will return this while the IsRedirected property is true
|
||||
request.RedirectUri = redirectUri;
|
||||
|
||||
request.IsRedirected = true;
|
||||
|
||||
resendRequest = true;
|
||||
}
|
||||
else
|
||||
throw new Exception(string.Format("[{0}] Got redirect status({1}) without 'location' header!", context, request.Response.StatusCode.ToString()));
|
||||
|
||||
goto default;
|
||||
}
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
case 304:
|
||||
if (request.DisableCache)
|
||||
break;
|
||||
|
||||
if (ConnectionHelper.LoadFromCache(context, request, loggingContext1, loggingContext2, loggingContext3))
|
||||
{
|
||||
request.Timing.Add(TimingEventNames.Loading_From_Cache);
|
||||
HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - HandleResponse - Loaded from cache successfully!", context), loggingContext1, loggingContext2, loggingContext3);
|
||||
|
||||
// Update any caching value
|
||||
HTTPCacheService.SetUpCachingValues(request.CurrentUri, request.Response);
|
||||
}
|
||||
else
|
||||
{
|
||||
HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - HandleResponse - Loaded from cache failed!", context), loggingContext1, loggingContext2, loggingContext3);
|
||||
resendRequest = true;
|
||||
}
|
||||
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
ConnectionHelper.TryStoreInCache(request);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
// Closing the stream is done manually?
|
||||
if (request.Response != null && !request.Response.IsClosedManually)
|
||||
{
|
||||
// If we have a response and the server telling us that it closed the connection after the message sent to us, then
|
||||
// we will close the connection too.
|
||||
bool closeByServer = request.Response.HasHeaderWithValue("connection", "close");
|
||||
bool closeByClient = !request.IsKeepAlive;
|
||||
|
||||
if (closeByServer || closeByClient)
|
||||
{
|
||||
proposedConnectionState = HTTPConnectionStates.Closed;
|
||||
}
|
||||
else if (request.Response != null)
|
||||
{
|
||||
var keepAliveheaderValues = request.Response.GetHeaderValues("keep-alive");
|
||||
if (keepAliveheaderValues != null && keepAliveheaderValues.Count > 0)
|
||||
{
|
||||
if (keepAlive == null)
|
||||
keepAlive = new KeepAliveHeader();
|
||||
keepAlive.Parse(keepAliveheaderValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Null out the response here instead of the redirected cases (301, 302, 307, 308)
|
||||
// because response might have a Connection: Close header that we would miss to process.
|
||||
// If Connection: Close is present, the server is closing the connection and we would
|
||||
// reuse that closed connection.
|
||||
if (resendRequest)
|
||||
{
|
||||
// Discard the redirect response, we don't need it any more
|
||||
request.Response = null;
|
||||
|
||||
if (proposedConnectionState == HTTPConnectionStates.Closed)
|
||||
proposedConnectionState = HTTPConnectionStates.ClosedResendRequest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
public static bool LoadFromCache(string context, HTTPRequest request, LoggingContext loggingContext1 = null, LoggingContext loggingContext2 = null, LoggingContext loggingContext3 = null)
|
||||
{
|
||||
if (request.IsRedirected)
|
||||
{
|
||||
if (LoadFromCache(context, request, request.RedirectUri, loggingContext1, loggingContext2, loggingContext3))
|
||||
return true;
|
||||
else
|
||||
{
|
||||
Caching.HTTPCacheService.DeleteEntity(request.RedirectUri);
|
||||
}
|
||||
}
|
||||
|
||||
bool loaded = LoadFromCache(context, request, request.Uri, loggingContext1, loggingContext2, loggingContext3);
|
||||
if (!loaded)
|
||||
Caching.HTTPCacheService.DeleteEntity(request.Uri);
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
private static bool LoadFromCache(string context, HTTPRequest request, Uri uri, LoggingContext loggingContext1 = null, LoggingContext loggingContext2 = null, LoggingContext loggingContext3 = null)
|
||||
{
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - LoadFromCache for Uri: {1}", context, uri.ToString()), loggingContext1, loggingContext2, loggingContext3);
|
||||
|
||||
var cacheEntity = HTTPCacheService.GetEntity(uri);
|
||||
if (cacheEntity == null)
|
||||
{
|
||||
HTTPManager.Logger.Warning("HTTPConnection", string.Format("[{0}] - LoadFromCache for Uri: {1} - Cached entity not found!", context, uri.ToString()), loggingContext1, loggingContext2, loggingContext3);
|
||||
return false;
|
||||
}
|
||||
|
||||
request.Response.CacheFileInfo = cacheEntity;
|
||||
|
||||
try
|
||||
{
|
||||
int bodyLength;
|
||||
using (var cacheStream = cacheEntity.GetBodyStream(out bodyLength))
|
||||
{
|
||||
if (cacheStream == null)
|
||||
return false;
|
||||
|
||||
if (!request.Response.HasHeader("content-length"))
|
||||
request.Response.AddHeader("content-length", bodyLength.ToString());
|
||||
request.Response.IsFromCache = true;
|
||||
|
||||
if (!request.CacheOnly)
|
||||
request.Response.ReadRaw(cacheStream, bodyLength);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryLoadAllFromCache(string context, HTTPRequest request, LoggingContext loggingContext1 = null, LoggingContext loggingContext2 = null, LoggingContext loggingContext3 = null)
|
||||
{
|
||||
// We will try to read the response from the cache, but if something happens we will fallback to the normal way.
|
||||
try
|
||||
{
|
||||
//Unless specifically constrained by a cache-control (section 14.9) directive, a caching system MAY always store a successful response (see section 13.8) as a cache entity,
|
||||
// MAY return it without validation if it is fresh, and MAY return it after successful validation.
|
||||
// MAY return it without validation if it is fresh!
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Verbose("ConnectionHelper", string.Format("[{0}] - TryLoadAllFromCache - whole response loading from cache", context), loggingContext1, loggingContext2, loggingContext3);
|
||||
|
||||
HTTPCacheService.GetFullResponse(request);
|
||||
|
||||
if (request.Response != null)
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
request.Response = null;
|
||||
HTTPManager.Logger.Verbose("ConnectionHelper", string.Format("[{0}] - TryLoadAllFromCache - failed to load content!", context), loggingContext1, loggingContext2, loggingContext3);
|
||||
HTTPCacheService.DeleteEntity(request.CurrentUri);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void TryStoreInCache(HTTPRequest request)
|
||||
{
|
||||
// if UseStreaming && !DisableCache then we already wrote the response to the cache
|
||||
if (!request.UseStreaming &&
|
||||
!request.DisableCache &&
|
||||
request.Response != null &&
|
||||
HTTPCacheService.IsSupported &&
|
||||
HTTPCacheService.IsCacheble(request.CurrentUri, request.MethodType, request.Response))
|
||||
{
|
||||
if (request.IsRedirected)
|
||||
HTTPCacheService.Store(request.Uri, request.MethodType, request.Response);
|
||||
else
|
||||
HTTPCacheService.Store(request.CurrentUri, request.MethodType, request.Response);
|
||||
request.Timing.Add(TimingEventNames.Writing_To_Cache);
|
||||
|
||||
PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public static Uri GetRedirectUri(HTTPRequest request, string location)
|
||||
{
|
||||
Uri result = null;
|
||||
try
|
||||
{
|
||||
result = new Uri(location);
|
||||
|
||||
if (result.IsFile || result.AbsolutePath == location)
|
||||
result = null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Sometimes the server sends back only the path and query component of the new uri
|
||||
result = null;
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
var baseURL = request.CurrentUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
|
||||
|
||||
if (!location.StartsWith("/"))
|
||||
{
|
||||
var segments = request.CurrentUri.Segments;
|
||||
segments[segments.Length - 1] = location;
|
||||
|
||||
location = String.Join(string.Empty, segments);
|
||||
if (location.StartsWith("//"))
|
||||
location = location.Substring(1);
|
||||
}
|
||||
|
||||
bool endsWithSlash = baseURL[baseURL.Length - 1] == '/';
|
||||
bool startsWithSlash = location[0] == '/';
|
||||
if (endsWithSlash && startsWithSlash)
|
||||
result = new Uri(baseURL + location.Substring(1));
|
||||
else if (!endsWithSlash && !startsWithSlash)
|
||||
result = new Uri(baseURL + '/' + location);
|
||||
else
|
||||
result = new Uri(baseURL + location);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using BestHTTP.Core;
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.PlatformSupport.FileSystem;
|
||||
|
||||
namespace BestHTTP.Connections
|
||||
{
|
||||
internal sealed class FileConnection : ConnectionBase
|
||||
{
|
||||
public FileConnection(string serverAddress)
|
||||
:base(serverAddress)
|
||||
{ }
|
||||
|
||||
protected override void ThreadFunc()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Step 1 : create a stream with header information
|
||||
// Step 2 : create a stream from the file
|
||||
// Step 3 : create a StreamList
|
||||
// Step 4 : create a HTTPResponse object
|
||||
// Step 5 : call the Receive function of the response object
|
||||
|
||||
using (System.IO.Stream fs = HTTPManager.IOService.CreateFileStream(this.CurrentRequest.CurrentUri.LocalPath, FileStreamModes.OpenRead))
|
||||
using (StreamList stream = new StreamList(new BufferPoolMemoryStream(), fs))
|
||||
{
|
||||
// This will write to the MemoryStream
|
||||
stream.Write("HTTP/1.1 200 Ok\r\n");
|
||||
stream.Write("Content-Type: application/octet-stream\r\n");
|
||||
stream.Write("Content-Length: " + fs.Length.ToString() + "\r\n");
|
||||
stream.Write("\r\n");
|
||||
|
||||
stream.Seek(0, System.IO.SeekOrigin.Begin);
|
||||
|
||||
base.CurrentRequest.Response = new HTTPResponse(base.CurrentRequest, stream, base.CurrentRequest.UseStreaming, false);
|
||||
|
||||
if (!CurrentRequest.Response.Receive())
|
||||
CurrentRequest.Response = null;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
CurrentRequest.Response = null;
|
||||
|
||||
if (!CurrentRequest.IsCancellationRequested)
|
||||
{
|
||||
CurrentRequest.Exception = e;
|
||||
CurrentRequest.State = HTTPRequestStates.Error;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (this.CurrentRequest.IsCancellationRequested)
|
||||
{
|
||||
this.CurrentRequest.Response = null;
|
||||
this.CurrentRequest.State = this.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
|
||||
}
|
||||
else if (this.CurrentRequest.Response == null)
|
||||
this.CurrentRequest.State = HTTPRequestStates.Error;
|
||||
else
|
||||
this.CurrentRequest.State = HTTPRequestStates.Finished;
|
||||
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
using System;
|
||||
using BestHTTP.Core;
|
||||
using BestHTTP.Logger;
|
||||
using BestHTTP.PlatformSupport.Threading;
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
using BestHTTP.Caching;
|
||||
#endif
|
||||
|
||||
using BestHTTP.Timings;
|
||||
|
||||
namespace BestHTTP.Connections
|
||||
{
|
||||
public sealed class HTTP1Handler : IHTTPRequestHandler
|
||||
{
|
||||
public bool HasCustomRequestProcessor { get { return false; } }
|
||||
public KeepAliveHeader KeepAlive { get { return this._keepAlive; } }
|
||||
private KeepAliveHeader _keepAlive;
|
||||
|
||||
public bool CanProcessMultiple { get { return false; } }
|
||||
|
||||
private readonly HTTPConnection conn;
|
||||
|
||||
public LoggingContext Context { get; private set; }
|
||||
|
||||
public HTTP1Handler(HTTPConnection conn)
|
||||
{
|
||||
this.Context = new LoggingContext(this);
|
||||
this.conn = conn;
|
||||
}
|
||||
|
||||
public void Process(HTTPRequest request)
|
||||
{
|
||||
}
|
||||
|
||||
public void RunHandler()
|
||||
{
|
||||
HTTPManager.Logger.Information("HTTP1Handler", string.Format("[{0}] started processing request '{1}'", this, this.conn.CurrentRequest.CurrentUri.ToString()), this.Context, this.conn.CurrentRequest.Context);
|
||||
|
||||
ThreadedRunner.SetThreadName("BestHTTP.HTTP1 R&W");
|
||||
|
||||
HTTPConnectionStates proposedConnectionState = HTTPConnectionStates.Processing;
|
||||
|
||||
bool resendRequest = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (this.conn.CurrentRequest.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
// Setup cache control headers before we send out the request
|
||||
if (!this.conn.CurrentRequest.DisableCache)
|
||||
HTTPCacheService.SetHeaders(this.conn.CurrentRequest);
|
||||
#endif
|
||||
|
||||
// Write the request to the stream
|
||||
this.conn.CurrentRequest.QueuedAt = DateTime.MinValue;
|
||||
this.conn.CurrentRequest.ProcessingStarted = DateTime.UtcNow;
|
||||
this.conn.CurrentRequest.SendOutTo(this.conn.connector.Stream);
|
||||
this.conn.CurrentRequest.Timing.Add(TimingEventNames.Request_Sent);
|
||||
|
||||
if (this.conn.CurrentRequest.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
this.conn.CurrentRequest.OnCancellationRequested += OnCancellationRequested;
|
||||
|
||||
// Receive response from the server
|
||||
bool received = Receive(this.conn.CurrentRequest);
|
||||
|
||||
this.conn.CurrentRequest.Timing.Add(TimingEventNames.Response_Received);
|
||||
|
||||
if (this.conn.CurrentRequest.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (!received && this.conn.CurrentRequest.Retries < this.conn.CurrentRequest.MaxRetries)
|
||||
{
|
||||
proposedConnectionState = HTTPConnectionStates.Closed;
|
||||
this.conn.CurrentRequest.Retries++;
|
||||
resendRequest = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionHelper.HandleResponse(this.conn.ToString(), this.conn.CurrentRequest, out resendRequest, out proposedConnectionState, ref this._keepAlive, this.conn.Context, this.conn.CurrentRequest.Context);
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
{
|
||||
this.conn.CurrentRequest.Response = null;
|
||||
|
||||
// Do nothing here if Abort() got called on the request, its State is already set.
|
||||
if (!this.conn.CurrentRequest.IsTimedOut)
|
||||
{
|
||||
// We will try again only once
|
||||
if (this.conn.CurrentRequest.Retries < this.conn.CurrentRequest.MaxRetries)
|
||||
{
|
||||
this.conn.CurrentRequest.Retries++;
|
||||
resendRequest = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.conn.CurrentRequest.Exception = e;
|
||||
this.conn.CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut;
|
||||
}
|
||||
}
|
||||
|
||||
proposedConnectionState = HTTPConnectionStates.Closed;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (this.ShutdownType == ShutdownTypes.Immediate)
|
||||
return;
|
||||
|
||||
string exceptionMessage = string.Empty;
|
||||
if (e == null)
|
||||
exceptionMessage = "null";
|
||||
else
|
||||
{
|
||||
System.Text.StringBuilder sb = PlatformSupport.Text.StringBuilderPool.Get(1);
|
||||
|
||||
Exception exception = e;
|
||||
int counter = 1;
|
||||
while (exception != null)
|
||||
{
|
||||
sb.AppendFormat("{0}: {1} {2}", counter++.ToString(), exception.Message, exception.StackTrace);
|
||||
|
||||
exception = exception.InnerException;
|
||||
|
||||
if (exception != null)
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
exceptionMessage = PlatformSupport.Text.StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
HTTPManager.Logger.Verbose("HTTP1Handler", exceptionMessage, this.Context, this.conn.CurrentRequest.Context);
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
if (this.conn.CurrentRequest.UseStreaming)
|
||||
HTTPCacheService.DeleteEntity(this.conn.CurrentRequest.CurrentUri);
|
||||
#endif
|
||||
|
||||
// Something gone bad, Response must be null!
|
||||
this.conn.CurrentRequest.Response = null;
|
||||
|
||||
// Do nothing here if Abort() got called on the request, its State is already set.
|
||||
if (!this.conn.CurrentRequest.IsCancellationRequested)
|
||||
{
|
||||
this.conn.CurrentRequest.Exception = e;
|
||||
this.conn.CurrentRequest.State = HTTPRequestStates.Error;
|
||||
}
|
||||
|
||||
proposedConnectionState = HTTPConnectionStates.Closed;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.conn.CurrentRequest.OnCancellationRequested -= OnCancellationRequested;
|
||||
|
||||
// Exit ASAP
|
||||
if (this.ShutdownType != ShutdownTypes.Immediate)
|
||||
{
|
||||
if (this.conn.CurrentRequest.IsCancellationRequested)
|
||||
{
|
||||
// we don't know what stage the request is canceled, we can't safely reuse the tcp channel.
|
||||
proposedConnectionState = HTTPConnectionStates.Closed;
|
||||
|
||||
this.conn.CurrentRequest.Response = null;
|
||||
|
||||
// The request's State already set, or going to be set soon in RequestEvents.cs.
|
||||
//this.conn.CurrentRequest.State = this.conn.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
|
||||
}
|
||||
else if (resendRequest)
|
||||
{
|
||||
// Here introducing a ClosedResendRequest connection state, where we have to process the connection's state change to Closed
|
||||
// than we have to resend the request.
|
||||
// If we would send the Resend request here, than a few lines below the Closed connection state change,
|
||||
// request events are processed before connection events (just switching the EnqueueRequestEvent and EnqueueConnectionEvent wouldn't work
|
||||
// see order of ProcessQueues in HTTPManager.OnUpdate!) and it would pick this very same closing/closed connection!
|
||||
|
||||
if (proposedConnectionState == HTTPConnectionStates.Closed || proposedConnectionState == HTTPConnectionStates.ClosedResendRequest)
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, this.conn.CurrentRequest));
|
||||
else
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.conn.CurrentRequest, RequestEvents.Resend));
|
||||
}
|
||||
else if (this.conn.CurrentRequest.Response != null && this.conn.CurrentRequest.Response.IsUpgraded)
|
||||
{
|
||||
proposedConnectionState = HTTPConnectionStates.WaitForProtocolShutdown;
|
||||
}
|
||||
else if (this.conn.CurrentRequest.State == HTTPRequestStates.Processing)
|
||||
{
|
||||
if (this.conn.CurrentRequest.Response != null)
|
||||
this.conn.CurrentRequest.State = HTTPRequestStates.Finished;
|
||||
else
|
||||
{
|
||||
this.conn.CurrentRequest.Exception = new Exception(string.Format("[{0}] Remote server closed the connection before sending response header! Previous request state: {1}. Connection state: {2}",
|
||||
this.ToString(),
|
||||
this.conn.CurrentRequest.State.ToString(),
|
||||
this.conn.State.ToString()));
|
||||
this.conn.CurrentRequest.State = HTTPRequestStates.Error;
|
||||
|
||||
proposedConnectionState = HTTPConnectionStates.Closed;
|
||||
}
|
||||
}
|
||||
|
||||
this.conn.CurrentRequest = null;
|
||||
|
||||
if (proposedConnectionState == HTTPConnectionStates.Processing)
|
||||
proposedConnectionState = HTTPConnectionStates.Recycle;
|
||||
|
||||
if (proposedConnectionState != HTTPConnectionStates.ClosedResendRequest)
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, proposedConnectionState));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCancellationRequested(HTTPRequest obj)
|
||||
{
|
||||
if (this.conn != null && this.conn.connector != null)
|
||||
this.conn.connector.Dispose();
|
||||
}
|
||||
|
||||
private bool Receive(HTTPRequest request)
|
||||
{
|
||||
SupportedProtocols protocol = HTTPProtocolFactory.GetProtocolFromUri(request.CurrentUri);
|
||||
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - Receive - protocol: {1}", this.ToString(), protocol.ToString()), this.Context, request.Context);
|
||||
|
||||
request.Response = HTTPProtocolFactory.Get(protocol, request, this.conn.connector.Stream, request.UseStreaming, false);
|
||||
|
||||
if (!request.Response.Receive())
|
||||
{
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Verbose("HTTP1Handler", string.Format("[{0}] - Receive - Failed! Response will be null, returning with false.", this.ToString()), this.Context, request.Context);
|
||||
request.Response = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Verbose("HTTP1Handler", string.Format("[{0}] - Receive - Finished Successfully!", this.ToString()), this.Context, request.Context);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ShutdownTypes ShutdownType { get; private set; }
|
||||
|
||||
public void Shutdown(ShutdownTypes type)
|
||||
{
|
||||
this.ShutdownType = type;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
|
||||
~HTTP1Handler()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
|
||||
using System;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
public static class BufferHelper
|
||||
{
|
||||
public static void SetUInt16(byte[] buffer, int offset, UInt16 value)
|
||||
{
|
||||
buffer[offset + 1] = (byte)(value);
|
||||
buffer[offset + 0] = (byte)(value >> 8);
|
||||
}
|
||||
|
||||
public static void SetUInt24(byte[] buffer, int offset, UInt32 value)
|
||||
{
|
||||
buffer[offset + 2] = (byte)(value);
|
||||
buffer[offset + 1] = (byte)(value >> 8);
|
||||
buffer[offset + 0] = (byte)(value >> 16);
|
||||
}
|
||||
|
||||
public static void SetUInt31(byte[] buffer, int offset, UInt32 value)
|
||||
{
|
||||
buffer[offset + 3] = (byte)(value);
|
||||
buffer[offset + 2] = (byte)(value >> 8);
|
||||
buffer[offset + 1] = (byte)(value >> 16);
|
||||
buffer[offset + 0] = (byte)((value >> 24) & 0x7F);
|
||||
}
|
||||
|
||||
public static void SetUInt32(byte[] buffer, int offset, UInt32 value)
|
||||
{
|
||||
buffer[offset + 3] = (byte)(value);
|
||||
buffer[offset + 2] = (byte)(value >> 8);
|
||||
buffer[offset + 1] = (byte)(value >> 16);
|
||||
buffer[offset + 0] = (byte)(value >> 24);
|
||||
}
|
||||
|
||||
public static void SetLong(byte[] buffer, int offset, long value)
|
||||
{
|
||||
buffer[offset + 7] = (byte)(value);
|
||||
buffer[offset + 6] = (byte)(value >> 8);
|
||||
buffer[offset + 5] = (byte)(value >> 16);
|
||||
buffer[offset + 4] = (byte)(value >> 24);
|
||||
buffer[offset + 3] = (byte)(value >> 32);
|
||||
buffer[offset + 2] = (byte)(value >> 40);
|
||||
buffer[offset + 1] = (byte)(value >> 48);
|
||||
buffer[offset + 0] = (byte)(value >> 56);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// bitIdx: 01234567
|
||||
/// </summary>
|
||||
public static byte SetBit(byte value, byte bitIdx, bool bitValue)
|
||||
{
|
||||
return SetBit(value, bitIdx, Convert.ToByte(bitValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// bitIdx: 01234567
|
||||
/// </summary>
|
||||
public static byte SetBit(byte value, byte bitIdx, byte bitValue)
|
||||
{
|
||||
//byte mask = (byte)(0x80 >> bitIdx);
|
||||
|
||||
return (byte)((value ^ (value & (0x80 >> bitIdx))) | bitValue << (7 - bitIdx));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// bitIdx: 01234567
|
||||
/// </summary>
|
||||
public static byte ReadBit(byte value, byte bitIdx)
|
||||
{
|
||||
byte mask = (byte)(0x80 >> bitIdx);
|
||||
|
||||
return (byte)((value & mask) >> (7 - bitIdx));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// bitIdx: 01234567
|
||||
/// </summary>
|
||||
public static byte ReadValue(byte value, byte fromBit, byte toBit)
|
||||
{
|
||||
byte result = 0;
|
||||
short idx = toBit;
|
||||
|
||||
while (idx >= fromBit)
|
||||
{
|
||||
result += (byte)(ReadBit(value, (byte)idx) << (toBit - idx));
|
||||
idx--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static UInt16 ReadUInt16(byte[] buffer, int offset)
|
||||
{
|
||||
return (UInt16)(buffer[offset + 1] | buffer[offset] << 8);
|
||||
}
|
||||
|
||||
public static UInt32 ReadUInt24(byte[] buffer, int offset)
|
||||
{
|
||||
return (UInt32)(buffer[offset + 2] |
|
||||
buffer[offset + 1] << 8 |
|
||||
buffer[offset + 0] << 16
|
||||
);
|
||||
}
|
||||
|
||||
public static UInt32 ReadUInt31(byte[] buffer, int offset)
|
||||
{
|
||||
return (UInt32)(buffer[offset + 3] |
|
||||
buffer[offset + 2] << 8 |
|
||||
buffer[offset + 1] << 16 |
|
||||
(buffer[offset] & 0x7F) << 24
|
||||
);
|
||||
}
|
||||
|
||||
public static UInt32 ReadUInt32(byte[] buffer, int offset)
|
||||
{
|
||||
return (UInt32)(buffer[offset + 3] |
|
||||
buffer[offset + 2] << 8 |
|
||||
buffer[offset + 1] << 16 |
|
||||
buffer[offset + 0] << 24
|
||||
);
|
||||
}
|
||||
|
||||
public static long ReadLong(byte[] buffer, int offset)
|
||||
{
|
||||
return (long)buffer[offset + 7] |
|
||||
(long)buffer[offset + 6] << 8 |
|
||||
(long)buffer[offset + 5] << 16 |
|
||||
(long)buffer[offset + 4] << 24 |
|
||||
(long)buffer[offset + 3] << 32 |
|
||||
(long)buffer[offset + 2] << 40 |
|
||||
(long)buffer[offset + 1] << 48 |
|
||||
(long)buffer[offset + 0] << 56;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
public interface IFrameDataView : IDisposable
|
||||
{
|
||||
long Length { get; }
|
||||
long Position { get; }
|
||||
|
||||
void AddFrame(HTTP2FrameHeaderAndPayload frame);
|
||||
int ReadByte();
|
||||
int Read(byte[] buffer, int offset, int count);
|
||||
}
|
||||
|
||||
public abstract class CommonFrameView : IFrameDataView
|
||||
{
|
||||
public long Length { get; protected set; }
|
||||
public long Position { get; protected set; }
|
||||
|
||||
protected List<HTTP2FrameHeaderAndPayload> frames = new List<HTTP2FrameHeaderAndPayload>();
|
||||
protected int currentFrameIdx = -1;
|
||||
protected byte[] data;
|
||||
protected UInt32 dataOffset;
|
||||
protected UInt32 maxOffset;
|
||||
|
||||
public abstract void AddFrame(HTTP2FrameHeaderAndPayload frame);
|
||||
protected abstract long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame);
|
||||
|
||||
public virtual int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (this.dataOffset >= this.maxOffset && !AdvanceFrame())
|
||||
return -1;
|
||||
|
||||
int readCount = 0;
|
||||
|
||||
while (count > 0)
|
||||
{
|
||||
long copyCount = Math.Min(count, this.maxOffset - this.dataOffset);
|
||||
|
||||
Array.Copy(this.data, this.dataOffset, buffer, offset + readCount, copyCount);
|
||||
|
||||
count -= (int)copyCount;
|
||||
readCount += (int)copyCount;
|
||||
|
||||
this.dataOffset += (UInt32)copyCount;
|
||||
this.Position += copyCount;
|
||||
|
||||
if (this.dataOffset >= this.maxOffset && !AdvanceFrame())
|
||||
break;
|
||||
}
|
||||
|
||||
return readCount;
|
||||
}
|
||||
|
||||
public virtual int ReadByte()
|
||||
{
|
||||
if (this.dataOffset >= this.maxOffset && !AdvanceFrame())
|
||||
return -1;
|
||||
|
||||
byte data = this.data[this.dataOffset];
|
||||
this.dataOffset++;
|
||||
this.Position++;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
protected abstract bool AdvanceFrame();
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
for (int i = 0; i < this.frames.Count; ++i)
|
||||
//if (this.frames[i].Payload != null && !this.frames[i].DontUseMemPool)
|
||||
BufferPool.Release(this.frames[i].Payload);
|
||||
this.frames.Clear();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = PlatformSupport.Text.StringBuilderPool.Get(this.frames.Count + 2);
|
||||
sb.Append("[CommonFrameView ");
|
||||
|
||||
for (int i = 0; i < this.frames.Count; ++i) {
|
||||
sb.AppendFormat("{0} Payload: {1}\n", this.frames[i], this.frames[i].PayloadAsHex());
|
||||
}
|
||||
|
||||
sb.Append("]");
|
||||
|
||||
return PlatformSupport.Text.StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class HeaderFrameView : CommonFrameView
|
||||
{
|
||||
public override void AddFrame(HTTP2FrameHeaderAndPayload frame)
|
||||
{
|
||||
if (frame.Type != HTTP2FrameTypes.HEADERS && frame.Type != HTTP2FrameTypes.CONTINUATION)
|
||||
throw new ArgumentException("HeaderFrameView - Unexpected frame type: " + frame.Type);
|
||||
|
||||
this.frames.Add(frame);
|
||||
this.Length += CalculateDataLengthForFrame(frame);
|
||||
|
||||
if (this.currentFrameIdx == -1)
|
||||
AdvanceFrame();
|
||||
}
|
||||
|
||||
protected override long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame)
|
||||
{
|
||||
switch (frame.Type)
|
||||
{
|
||||
case HTTP2FrameTypes.HEADERS:
|
||||
return HTTP2FrameHelper.ReadHeadersFrame(frame).HeaderBlockFragmentLength;
|
||||
|
||||
case HTTP2FrameTypes.CONTINUATION:
|
||||
return frame.PayloadLength;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected override bool AdvanceFrame()
|
||||
{
|
||||
if (this.currentFrameIdx >= this.frames.Count - 1)
|
||||
return false;
|
||||
|
||||
this.currentFrameIdx++;
|
||||
HTTP2FrameHeaderAndPayload frame = this.frames[this.currentFrameIdx];
|
||||
|
||||
this.data = frame.Payload;
|
||||
|
||||
switch (frame.Type)
|
||||
{
|
||||
case HTTP2FrameTypes.HEADERS:
|
||||
var header = HTTP2FrameHelper.ReadHeadersFrame(frame);
|
||||
this.dataOffset = header.HeaderBlockFragmentIdx;
|
||||
this.maxOffset = this.dataOffset + header.HeaderBlockFragmentLength;
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.CONTINUATION:
|
||||
this.dataOffset = 0;
|
||||
this.maxOffset = frame.PayloadLength;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DataFrameView : CommonFrameView
|
||||
{
|
||||
public override void AddFrame(HTTP2FrameHeaderAndPayload frame)
|
||||
{
|
||||
if (frame.Type != HTTP2FrameTypes.DATA)
|
||||
throw new ArgumentException("HeaderFrameView - Unexpected frame type: " + frame.Type);
|
||||
|
||||
this.frames.Add(frame);
|
||||
this.Length += CalculateDataLengthForFrame(frame);
|
||||
}
|
||||
|
||||
protected override long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame)
|
||||
{
|
||||
return HTTP2FrameHelper.ReadDataFrame(frame).DataLength;
|
||||
}
|
||||
|
||||
protected override bool AdvanceFrame()
|
||||
{
|
||||
if (this.currentFrameIdx >= this.frames.Count - 1)
|
||||
return false;
|
||||
|
||||
this.currentFrameIdx++;
|
||||
HTTP2FrameHeaderAndPayload frame = this.frames[this.currentFrameIdx];
|
||||
HTTP2DataFrame dataFrame = HTTP2FrameHelper.ReadDataFrame(frame);
|
||||
|
||||
this.data = frame.Payload;
|
||||
this.dataOffset = dataFrame.DataIdx;
|
||||
this.maxOffset = dataFrame.DataIdx + dataFrame.DataLength;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FramesAsStreamView : Stream
|
||||
{
|
||||
public override bool CanRead { get { return true; } }
|
||||
public override bool CanSeek { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
public override long Length { get { return this.view.Length; } }
|
||||
public override long Position { get { return this.view.Position; } set { throw new NotSupportedException(); } }
|
||||
|
||||
private IFrameDataView view;
|
||||
|
||||
public FramesAsStreamView(IFrameDataView view)
|
||||
{
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
public void AddFrame(HTTP2FrameHeaderAndPayload frame)
|
||||
{
|
||||
this.view.AddFrame(frame);
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
return this.view.ReadByte();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return this.view.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
this.view.Dispose();
|
||||
}
|
||||
|
||||
public override void Flush() {}
|
||||
public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); }
|
||||
public override void SetLength(long value) { throw new NotImplementedException(); }
|
||||
public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.view.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,798 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
public sealed class HPACKEncoder
|
||||
{
|
||||
private HTTP2SettingsManager settingsRegistry;
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#encoding.context
|
||||
// When used for bidirectional communication, such as in HTTP, the encoding and decoding dynamic tables
|
||||
// maintained by an endpoint are completely independent, i.e., the request and response dynamic tables are separate.
|
||||
private HeaderTable requestTable;
|
||||
private HeaderTable responseTable;
|
||||
|
||||
private HTTP2Handler parent;
|
||||
|
||||
public HPACKEncoder(HTTP2Handler parentHandler, HTTP2SettingsManager registry)
|
||||
{
|
||||
this.parent = parentHandler;
|
||||
this.settingsRegistry = registry;
|
||||
|
||||
// I'm unsure what settings (local or remote) we should use for these two tables!
|
||||
this.requestTable = new HeaderTable(this.settingsRegistry.MySettings);
|
||||
this.responseTable = new HeaderTable(this.settingsRegistry.RemoteSettings);
|
||||
}
|
||||
|
||||
public void Encode(HTTP2Stream context, HTTPRequest request, Queue<HTTP2FrameHeaderAndPayload> to, UInt32 streamId)
|
||||
{
|
||||
// Add usage of SETTINGS_MAX_HEADER_LIST_SIZE to be able to create a header and one or more continuation fragments
|
||||
// (https://httpwg.org/specs/rfc7540.html#SettingValues)
|
||||
|
||||
using (BufferPoolMemoryStream bufferStream = new BufferPoolMemoryStream(HTTPRequest.UploadChunkSize))
|
||||
{
|
||||
WriteHeader(bufferStream, ":method", HTTPRequest.MethodNames[(int)request.MethodType]);
|
||||
// add path
|
||||
WriteHeader(bufferStream, ":path", request.CurrentUri.PathAndQuery);
|
||||
// add authority
|
||||
WriteHeader(bufferStream, ":authority", request.CurrentUri.Authority);
|
||||
// add scheme
|
||||
WriteHeader(bufferStream, ":scheme", "https");
|
||||
|
||||
//bool hasBody = false;
|
||||
|
||||
// add other, regular headers
|
||||
request.EnumerateHeaders((header, values) =>
|
||||
{
|
||||
if (header.Equals("connection", StringComparison.OrdinalIgnoreCase) ||
|
||||
(header.Equals("te", StringComparison.OrdinalIgnoreCase) && !values.Contains("trailers") && values.Count <= 1) ||
|
||||
header.Equals("host", StringComparison.OrdinalIgnoreCase) ||
|
||||
header.Equals("keep-alive", StringComparison.OrdinalIgnoreCase) ||
|
||||
header.StartsWith("proxy-", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
//if (!hasBody)
|
||||
// hasBody = header.Equals("content-length", StringComparison.OrdinalIgnoreCase) && int.Parse(values[0]) > 0;
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#HttpSequence
|
||||
// The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in HTTP/2.
|
||||
if (header.Equals("Transfer-Encoding", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// error!
|
||||
return;
|
||||
}
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#HttpHeaders
|
||||
// Just as in HTTP/1.x, header field names are strings of ASCII characters that are compared in a case-insensitive fashion.
|
||||
// However, header field names MUST be converted to lowercase prior to their encoding in HTTP/2.
|
||||
// A request or response containing uppercase header field names MUST be treated as malformed
|
||||
if (header.Any(Char.IsUpper))
|
||||
header = header.ToLowerInvariant();
|
||||
|
||||
for (int i = 0; i < values.Count; ++i)
|
||||
{
|
||||
WriteHeader(bufferStream, header, values[i]);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] - Encode - Header({1}/{2}): '{3}': '{4}'", context.Id, i + 1, values.Count, header, values[i]), this.parent.Context, context.Context, request.Context);
|
||||
}
|
||||
}, true);
|
||||
|
||||
var upStreamInfo = request.GetUpStream();
|
||||
CreateHeaderFrames(to,
|
||||
streamId,
|
||||
bufferStream.ToArray(true),
|
||||
(UInt32)bufferStream.Length,
|
||||
upStreamInfo.Stream != null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Decode(HTTP2Stream context, Stream stream, List<KeyValuePair<string, string>> to)
|
||||
{
|
||||
int headerType = stream.ReadByte();
|
||||
while (headerType != -1)
|
||||
{
|
||||
byte firstDataByte = (byte)headerType;
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#indexed.header.representation
|
||||
if (BufferHelper.ReadBit(firstDataByte, 0) == 1)
|
||||
{
|
||||
var header = ReadIndexedHeader(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - IndexedHeader: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
|
||||
|
||||
to.Add(header);
|
||||
}
|
||||
else if (BufferHelper.ReadValue(firstDataByte, 0, 1) == 1)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
|
||||
|
||||
if (BufferHelper.ReadValue(firstDataByte, 2, 7) == 0)
|
||||
{
|
||||
// Literal Header Field with Incremental Indexing — New Name
|
||||
var header = ReadLiteralHeaderFieldWithIncrementalIndexing_NewName(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_NewName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
|
||||
|
||||
this.responseTable.Add(header);
|
||||
to.Add(header);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Literal Header Field with Incremental Indexing — Indexed Name
|
||||
var header = ReadLiteralHeaderFieldWithIncrementalIndexing_IndexedName(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_IndexedName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
|
||||
|
||||
this.responseTable.Add(header);
|
||||
to.Add(header);
|
||||
}
|
||||
} else if (BufferHelper.ReadValue(firstDataByte, 0, 3) == 0)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
|
||||
|
||||
if (BufferHelper.ReadValue(firstDataByte, 4, 7) == 0)
|
||||
{
|
||||
// Literal Header Field without Indexing — New Name
|
||||
var header = ReadLiteralHeaderFieldwithoutIndexing_NewName(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_NewName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
|
||||
|
||||
to.Add(header);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Literal Header Field without Indexing — Indexed Name
|
||||
var header = ReadLiteralHeaderFieldwithoutIndexing_IndexedName(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_IndexedName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
|
||||
|
||||
to.Add(header);
|
||||
}
|
||||
}
|
||||
else if (BufferHelper.ReadValue(firstDataByte, 0, 3) == 1)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
|
||||
|
||||
if (BufferHelper.ReadValue(firstDataByte, 4, 7) == 0)
|
||||
{
|
||||
// Literal Header Field Never Indexed — New Name
|
||||
var header = ReadLiteralHeaderFieldNeverIndexed_NewName(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_NewName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
|
||||
|
||||
to.Add(header);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Literal Header Field Never Indexed — Indexed Name
|
||||
var header = ReadLiteralHeaderFieldNeverIndexed_IndexedName(firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_IndexedName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
|
||||
|
||||
to.Add(header);
|
||||
}
|
||||
}
|
||||
else if (BufferHelper.ReadValue(firstDataByte, 0, 2) == 1)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#encoding.context.update
|
||||
|
||||
UInt32 newMaxSize = DecodeInteger(5, firstDataByte, stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - Dynamic Table Size Update: {1}", context.Id, newMaxSize), this.parent.Context, context.Context, context.AssignedRequest.Context);
|
||||
|
||||
//this.settingsRegistry[HTTP2Settings.HEADER_TABLE_SIZE] = (UInt16)newMaxSize;
|
||||
this.responseTable.MaxDynamicTableSize = (UInt16)newMaxSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ERROR
|
||||
}
|
||||
|
||||
headerType = stream.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadIndexedHeader(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#indexed.header.representation
|
||||
|
||||
UInt32 index = DecodeInteger(7, firstByte, stream);
|
||||
return this.responseTable.GetHeader(index);
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadLiteralHeaderFieldWithIncrementalIndexing_IndexedName(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
|
||||
|
||||
UInt32 keyIndex = DecodeInteger(6, firstByte, stream);
|
||||
|
||||
string header = this.responseTable.GetKey(keyIndex);
|
||||
string value = DecodeString(stream);
|
||||
|
||||
return new KeyValuePair<string, string>(header, value);
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadLiteralHeaderFieldWithIncrementalIndexing_NewName(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
|
||||
|
||||
string header = DecodeString(stream);
|
||||
string value = DecodeString(stream);
|
||||
|
||||
return new KeyValuePair<string, string>(header, value);
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadLiteralHeaderFieldwithoutIndexing_IndexedName(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
|
||||
|
||||
UInt32 index = DecodeInteger(4, firstByte, stream);
|
||||
string header = this.responseTable.GetKey(index);
|
||||
string value = DecodeString(stream);
|
||||
|
||||
return new KeyValuePair<string, string>(header, value);
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadLiteralHeaderFieldwithoutIndexing_NewName(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
|
||||
|
||||
string header = DecodeString(stream);
|
||||
string value = DecodeString(stream);
|
||||
|
||||
return new KeyValuePair<string, string>(header, value);
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadLiteralHeaderFieldNeverIndexed_IndexedName(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
|
||||
|
||||
UInt32 index = DecodeInteger(4, firstByte, stream);
|
||||
string header = this.responseTable.GetKey(index);
|
||||
string value = DecodeString(stream);
|
||||
|
||||
return new KeyValuePair<string, string>(header, value);
|
||||
}
|
||||
|
||||
private KeyValuePair<string, string> ReadLiteralHeaderFieldNeverIndexed_NewName(byte firstByte, Stream stream)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
|
||||
|
||||
string header = DecodeString(stream);
|
||||
string value = DecodeString(stream);
|
||||
|
||||
return new KeyValuePair<string, string>(header, value);
|
||||
}
|
||||
|
||||
private string DecodeString(Stream stream)
|
||||
{
|
||||
byte start = (byte)stream.ReadByte();
|
||||
bool rawString = BufferHelper.ReadBit(start, 0) == 0;
|
||||
UInt32 stringLength = DecodeInteger(7, start, stream);
|
||||
|
||||
if (stringLength == 0)
|
||||
return string.Empty;
|
||||
|
||||
if (rawString)
|
||||
{
|
||||
byte[] buffer = BufferPool.Get(stringLength, true);
|
||||
|
||||
stream.Read(buffer, 0, (int)stringLength);
|
||||
|
||||
var result = System.Text.Encoding.UTF8.GetString(buffer, 0, (int)stringLength);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
var node = HuffmanEncoder.GetRoot();
|
||||
byte currentByte = (byte)stream.ReadByte();
|
||||
byte bitIdx = 0; // 0..7
|
||||
|
||||
using (BufferPoolMemoryStream bufferStream = new BufferPoolMemoryStream((int)(stringLength * 1.5f)))
|
||||
{
|
||||
do
|
||||
{
|
||||
byte bitValue = BufferHelper.ReadBit(currentByte, bitIdx);
|
||||
|
||||
if (++bitIdx > 7)
|
||||
{
|
||||
stringLength--;
|
||||
|
||||
if (stringLength > 0)
|
||||
{
|
||||
bitIdx = 0;
|
||||
currentByte = (byte)stream.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
node = HuffmanEncoder.GetNext(node, bitValue);
|
||||
|
||||
if (node.Value != 0)
|
||||
{
|
||||
if (node.Value != HuffmanEncoder.EOS)
|
||||
bufferStream.WriteByte((byte)node.Value);
|
||||
|
||||
node = HuffmanEncoder.GetRoot();
|
||||
}
|
||||
} while (stringLength > 0);
|
||||
|
||||
byte[] buffer = bufferStream.GetBuffer();
|
||||
|
||||
string result = System.Text.Encoding.UTF8.GetString(buffer, 0, (int)bufferStream.Length);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateHeaderFrames(Queue<HTTP2FrameHeaderAndPayload> to, UInt32 streamId, byte[] dataToSend, UInt32 payloadLength, bool hasBody)
|
||||
{
|
||||
UInt32 maxFrameSize = this.settingsRegistry.RemoteSettings[HTTP2Settings.MAX_FRAME_SIZE];
|
||||
|
||||
// Only one headers frame
|
||||
if (payloadLength <= maxFrameSize)
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload frameHeader = new HTTP2FrameHeaderAndPayload();
|
||||
frameHeader.Type = HTTP2FrameTypes.HEADERS;
|
||||
frameHeader.StreamId = streamId;
|
||||
frameHeader.Flags = (byte)(HTTP2HeadersFlags.END_HEADERS);
|
||||
|
||||
if (!hasBody)
|
||||
frameHeader.Flags |= (byte)(HTTP2HeadersFlags.END_STREAM);
|
||||
|
||||
frameHeader.PayloadLength = payloadLength;
|
||||
frameHeader.Payload = dataToSend;
|
||||
|
||||
to.Enqueue(frameHeader);
|
||||
}
|
||||
else
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload frameHeader = new HTTP2FrameHeaderAndPayload();
|
||||
frameHeader.Type = HTTP2FrameTypes.HEADERS;
|
||||
frameHeader.StreamId = streamId;
|
||||
frameHeader.PayloadLength = maxFrameSize;
|
||||
frameHeader.Payload = dataToSend;
|
||||
frameHeader.DontUseMemPool = true;
|
||||
frameHeader.PayloadOffset = 0;
|
||||
|
||||
if (!hasBody)
|
||||
frameHeader.Flags = (byte)(HTTP2HeadersFlags.END_STREAM);
|
||||
|
||||
to.Enqueue(frameHeader);
|
||||
|
||||
UInt32 offset = maxFrameSize;
|
||||
while (offset < payloadLength)
|
||||
{
|
||||
frameHeader = new HTTP2FrameHeaderAndPayload();
|
||||
frameHeader.Type = HTTP2FrameTypes.CONTINUATION;
|
||||
frameHeader.StreamId = streamId;
|
||||
frameHeader.PayloadLength = maxFrameSize;
|
||||
frameHeader.Payload = dataToSend;
|
||||
frameHeader.PayloadOffset = offset;
|
||||
|
||||
offset += maxFrameSize;
|
||||
|
||||
if (offset >= payloadLength)
|
||||
{
|
||||
frameHeader.Flags = (byte)(HTTP2ContinuationFlags.END_HEADERS);
|
||||
// last sent continuation fragment will release back the payload buffer
|
||||
frameHeader.DontUseMemPool = false;
|
||||
}
|
||||
else
|
||||
frameHeader.DontUseMemPool = true;
|
||||
|
||||
to.Enqueue(frameHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteHeader(Stream stream, string header, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#header.representation
|
||||
|
||||
KeyValuePair<UInt32, UInt32> index = this.requestTable.GetIndex(header, value);
|
||||
|
||||
if (index.Key == 0 && index.Value == 0)
|
||||
{
|
||||
WriteLiteralHeaderFieldWithIncrementalIndexing_NewName(stream, header, value);
|
||||
this.requestTable.Add(new KeyValuePair<string, string>(header, value));
|
||||
}
|
||||
else if (index.Key != 0 && index.Value == 0)
|
||||
{
|
||||
WriteLiteralHeaderFieldWithIncrementalIndexing_IndexedName(stream, index.Key, value);
|
||||
this.requestTable.Add(new KeyValuePair<string, string>(header, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteIndexedHeaderField(stream, index.Key);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteIndexedHeaderField(Stream stream, UInt32 index)
|
||||
{
|
||||
byte requiredBytes = RequiredBytesToEncodeInteger(index, 7);
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[0] = 0x80;
|
||||
EncodeInteger(index, 7, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteLiteralHeaderFieldWithIncrementalIndexing_IndexedName(Stream stream, UInt32 index, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
|
||||
|
||||
UInt32 requiredBytes = RequiredBytesToEncodeInteger(index, 6) +
|
||||
RequiredBytesToEncodeString(value);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[0] = 0x40;
|
||||
EncodeInteger(index, 6, buffer, ref offset);
|
||||
EncodeString(value, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteLiteralHeaderFieldWithIncrementalIndexing_NewName(Stream stream, string header, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
|
||||
|
||||
UInt32 requiredBytes = 1 + RequiredBytesToEncodeString(header) + RequiredBytesToEncodeString(value);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[offset++] = 0x40;
|
||||
EncodeString(header, buffer, ref offset);
|
||||
EncodeString(value, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteLiteralHeaderFieldWithoutIndexing_IndexedName(Stream stream, UInt32 index, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
|
||||
|
||||
UInt32 requiredBytes = RequiredBytesToEncodeInteger(index, 4) + RequiredBytesToEncodeString(value);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[0] = 0;
|
||||
EncodeInteger(index, 4, buffer, ref offset);
|
||||
EncodeString(value, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteLiteralHeaderFieldWithoutIndexing_NewName(Stream stream, string header, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
|
||||
|
||||
UInt32 requiredBytes = 1 + RequiredBytesToEncodeString(header) + RequiredBytesToEncodeString(value);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[offset++] = 0;
|
||||
EncodeString(header, buffer, ref offset);
|
||||
EncodeString(value, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteLiteralHeaderFieldNeverIndexed_IndexedName(Stream stream, UInt32 index, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
|
||||
|
||||
UInt32 requiredBytes = RequiredBytesToEncodeInteger(index, 4) + RequiredBytesToEncodeString(value);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[0] = 0x10;
|
||||
EncodeInteger(index, 4, buffer, ref offset);
|
||||
EncodeString(value, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteLiteralHeaderFieldNeverIndexed_NewName(Stream stream, string header, string value)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
|
||||
|
||||
UInt32 requiredBytes = 1 + RequiredBytesToEncodeString(header) + RequiredBytesToEncodeString(value);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[offset++] = 0x10;
|
||||
EncodeString(header, buffer, ref offset);
|
||||
EncodeString(value, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static void WriteDynamicTableSizeUpdate(Stream stream, UInt16 maxSize)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#encoding.context.update
|
||||
|
||||
UInt32 requiredBytes = RequiredBytesToEncodeInteger(maxSize, 5);
|
||||
|
||||
byte[] buffer = BufferPool.Get(requiredBytes, true);
|
||||
UInt32 offset = 0;
|
||||
|
||||
buffer[offset] = 0x20;
|
||||
EncodeInteger(maxSize, 5, buffer, ref offset);
|
||||
|
||||
stream.Write(buffer, 0, (int)offset);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
private static UInt32 RequiredBytesToEncodeString(string str)
|
||||
{
|
||||
uint requiredBytesForRawStr = RequiredBytesToEncodeRawString(str);
|
||||
uint requiredBytesForHuffman = RequiredBytesToEncodeStringWithHuffman(str);
|
||||
requiredBytesForHuffman += RequiredBytesToEncodeInteger(requiredBytesForHuffman, 7);
|
||||
|
||||
return Math.Min(requiredBytesForRawStr, requiredBytesForHuffman);
|
||||
}
|
||||
|
||||
private static void EncodeString(string str, byte[] buffer, ref UInt32 offset)
|
||||
{
|
||||
uint requiredBytesForRawStr = RequiredBytesToEncodeRawString(str);
|
||||
uint requiredBytesForHuffman = RequiredBytesToEncodeStringWithHuffman(str);
|
||||
|
||||
// if using huffman encoding would produce the same length, we choose raw encoding instead as it requires
|
||||
// less CPU cicles
|
||||
if (requiredBytesForRawStr <= requiredBytesForHuffman + RequiredBytesToEncodeInteger(requiredBytesForHuffman, 7))
|
||||
EncodeRawStringTo(str, buffer, ref offset);
|
||||
else
|
||||
EncodeStringWithHuffman(str, requiredBytesForHuffman, buffer, ref offset);
|
||||
}
|
||||
|
||||
// This calculates only the length of the compressed string,
|
||||
// additional header length must be calculated using the value returned by this function
|
||||
private static UInt32 RequiredBytesToEncodeStringWithHuffman(string str)
|
||||
{
|
||||
int requiredBytesForStr = System.Text.Encoding.UTF8.GetByteCount(str);
|
||||
byte[] strBytes = BufferPool.Get(requiredBytesForStr, true);
|
||||
|
||||
System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, strBytes, 0);
|
||||
|
||||
UInt32 requiredBits = 0;
|
||||
|
||||
for (int i = 0; i < requiredBytesForStr; ++i)
|
||||
requiredBits += HuffmanEncoder.GetEntryForCodePoint(strBytes[i]).Bits;
|
||||
|
||||
BufferPool.Release(strBytes);
|
||||
|
||||
return (UInt32)((requiredBits / 8) + ((requiredBits % 8) == 0 ? 0 : 1));
|
||||
}
|
||||
|
||||
private static void EncodeStringWithHuffman(string str, UInt32 encodedLength, byte[] buffer, ref UInt32 offset)
|
||||
{
|
||||
int requiredBytesForStr = System.Text.Encoding.UTF8.GetByteCount(str);
|
||||
byte[] strBytes = BufferPool.Get(requiredBytesForStr, true);
|
||||
|
||||
System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, strBytes, 0);
|
||||
|
||||
// 0. bit: huffman flag
|
||||
buffer[offset] = 0x80;
|
||||
|
||||
// 1..7+ bit: length
|
||||
EncodeInteger(encodedLength, 7, buffer, ref offset);
|
||||
|
||||
byte bufferBitIdx = 0;
|
||||
|
||||
for (int i = 0; i < requiredBytesForStr; ++i)
|
||||
AddCodePointToBuffer(HuffmanEncoder.GetEntryForCodePoint(strBytes[i]), buffer, ref offset, ref bufferBitIdx);
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#string.literal.representation
|
||||
// As the Huffman-encoded data doesn't always end at an octet boundary, some padding is inserted after it,
|
||||
// up to the next octet boundary. To prevent this padding from being misinterpreted as part of the string literal,
|
||||
// the most significant bits of the code corresponding to the EOS (end-of-string) symbol are used.
|
||||
if (bufferBitIdx != 0)
|
||||
AddCodePointToBuffer(HuffmanEncoder.GetEntryForCodePoint(256), buffer, ref offset, ref bufferBitIdx, true);
|
||||
|
||||
BufferPool.Release(strBytes);
|
||||
}
|
||||
|
||||
private static void AddCodePointToBuffer(HuffmanEncoder.TableEntry code, byte[] buffer, ref UInt32 offset, ref byte bufferBitIdx, bool finishOnBoundary = false)
|
||||
{
|
||||
for (byte codeBitIdx = 1; codeBitIdx <= code.Bits; ++codeBitIdx)
|
||||
{
|
||||
byte bit = code.GetBitAtIdx(codeBitIdx);
|
||||
buffer[offset] = BufferHelper.SetBit(buffer[offset], bufferBitIdx, bit);
|
||||
|
||||
// octet boundary reached, proceed to the next octet
|
||||
if (++bufferBitIdx == 8)
|
||||
{
|
||||
if (++offset < buffer.Length)
|
||||
buffer[offset] = 0;
|
||||
|
||||
if (finishOnBoundary)
|
||||
return;
|
||||
|
||||
bufferBitIdx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static UInt32 RequiredBytesToEncodeRawString(string str)
|
||||
{
|
||||
int requiredBytesForStr = System.Text.Encoding.UTF8.GetByteCount(str);
|
||||
int requiredBytesForLengthPrefix = RequiredBytesToEncodeInteger((UInt32)requiredBytesForStr, 7);
|
||||
|
||||
return (UInt32)(requiredBytesForStr + requiredBytesForLengthPrefix);
|
||||
}
|
||||
|
||||
// This method encodes a string without huffman encoding
|
||||
private static void EncodeRawStringTo(string str, byte[] buffer, ref UInt32 offset)
|
||||
{
|
||||
uint requiredBytesForStr = (uint)System.Text.Encoding.UTF8.GetByteCount(str);
|
||||
int requiredBytesForLengthPrefix = RequiredBytesToEncodeInteger((UInt32)requiredBytesForStr, 7);
|
||||
|
||||
UInt32 originalOffset = offset;
|
||||
buffer[offset] = 0;
|
||||
EncodeInteger(requiredBytesForStr, 7, buffer, ref offset);
|
||||
|
||||
// Zero out the huffman flag
|
||||
buffer[originalOffset] = BufferHelper.SetBit(buffer[originalOffset], 0, false);
|
||||
|
||||
if (offset != originalOffset + requiredBytesForLengthPrefix)
|
||||
throw new Exception(string.Format("offset({0}) != originalOffset({1}) + requiredBytesForLengthPrefix({1})", offset, originalOffset, requiredBytesForLengthPrefix));
|
||||
|
||||
System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, (int)offset);
|
||||
offset += requiredBytesForStr;
|
||||
}
|
||||
|
||||
private static byte RequiredBytesToEncodeInteger(UInt32 value, byte N)
|
||||
{
|
||||
UInt32 maxValue = (1u << N) - 1;
|
||||
byte count = 0;
|
||||
|
||||
// If the integer value is small enough, i.e., strictly less than 2^N-1, it is encoded within the N-bit prefix.
|
||||
if (value < maxValue)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, all the bits of the prefix are set to 1, and the value, decreased by 2^N-1
|
||||
count++;
|
||||
value -= maxValue;
|
||||
|
||||
while (value >= 0x80)
|
||||
{
|
||||
// The most significant bit of each octet is used as a continuation flag: its value is set to 1 except for the last octet in the list.
|
||||
count++;
|
||||
value = value / 0x80;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#integer.representation
|
||||
private static void EncodeInteger(UInt32 value, byte N, byte[] buffer, ref UInt32 offset)
|
||||
{
|
||||
// 2^N - 1
|
||||
UInt32 maxValue = (1u << N) - 1;
|
||||
|
||||
// If the integer value is small enough, i.e., strictly less than 2^N-1, it is encoded within the N-bit prefix.
|
||||
if (value < maxValue)
|
||||
{
|
||||
buffer[offset++] |= (byte)value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, all the bits of the prefix are set to 1, and the value, decreased by 2^N-1
|
||||
buffer[offset++] |= (byte)(0xFF >> (8 - N));
|
||||
value -= maxValue;
|
||||
|
||||
while (value >= 0x80)
|
||||
{
|
||||
// The most significant bit of each octet is used as a continuation flag: its value is set to 1 except for the last octet in the list.
|
||||
buffer[offset++] = (byte)(0x80 | (0x7F & value));
|
||||
value = value / 0x80;
|
||||
}
|
||||
|
||||
buffer[offset++] = (byte)value;
|
||||
}
|
||||
}
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#integer.representation
|
||||
private static UInt32 DecodeInteger(byte N, byte[] buffer, ref UInt32 offset)
|
||||
{
|
||||
// The starting value is the value behind the mask of the N bits
|
||||
UInt32 value = (UInt32)(buffer[offset++] & (byte)(0xFF >> (8 - N)));
|
||||
|
||||
// All N bits are 1s ? If so, we have at least one another byte to decode
|
||||
if (value == (1u << N) - 1)
|
||||
{
|
||||
byte shift = 0;
|
||||
|
||||
do
|
||||
{
|
||||
// The most significant bit is a continuation flag, so we have to mask it out
|
||||
value += (UInt32)((buffer[offset] & 0x7F) << shift);
|
||||
shift += 7;
|
||||
} while ((buffer[offset++] & 0x80) == 0x80);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#integer.representation
|
||||
private static UInt32 DecodeInteger(byte N, byte data, Stream stream)
|
||||
{
|
||||
// The starting value is the value behind the mask of the N bits
|
||||
UInt32 value = (UInt32)(data & (byte)(0xFF >> (8 - N)));
|
||||
|
||||
// All N bits are 1s ? If so, we have at least one another byte to decode
|
||||
if (value == (1u << N) - 1)
|
||||
{
|
||||
byte shift = 0;
|
||||
|
||||
do
|
||||
{
|
||||
data = (byte)stream.ReadByte();
|
||||
|
||||
// The most significant bit is a continuation flag, so we have to mask it out
|
||||
value += (UInt32)((data & 0x7F) << shift);
|
||||
shift += 7;
|
||||
} while ((data & 0x80) == 0x80);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.requestTable.ToString() + this.responseTable.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,402 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#ErrorCodes
|
||||
public enum HTTP2ErrorCodes
|
||||
{
|
||||
NO_ERROR = 0x00,
|
||||
PROTOCOL_ERROR = 0x01,
|
||||
INTERNAL_ERROR = 0x02,
|
||||
FLOW_CONTROL_ERROR = 0x03,
|
||||
SETTINGS_TIMEOUT = 0x04,
|
||||
STREAM_CLOSED = 0x05,
|
||||
FRAME_SIZE_ERROR = 0x06,
|
||||
REFUSED_STREAM = 0x07,
|
||||
CANCEL = 0x08,
|
||||
COMPRESSION_ERROR = 0x09,
|
||||
CONNECT_ERROR = 0x0A,
|
||||
ENHANCE_YOUR_CALM = 0x0B,
|
||||
INADEQUATE_SECURITY = 0x0C,
|
||||
HTTP_1_1_REQUIRED = 0x0D
|
||||
}
|
||||
|
||||
public static class HTTP2FrameHelper
|
||||
{
|
||||
public static HTTP2ContinuationFrame ReadContinuationFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#CONTINUATION
|
||||
|
||||
HTTP2ContinuationFrame frame = new HTTP2ContinuationFrame(header);
|
||||
|
||||
frame.HeaderBlockFragment = header.Payload;
|
||||
header.Payload = null;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2WindowUpdateFrame ReadWindowUpdateFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE
|
||||
|
||||
HTTP2WindowUpdateFrame frame = new HTTP2WindowUpdateFrame(header);
|
||||
|
||||
frame.ReservedBit = BufferHelper.ReadBit(header.Payload[0], 0);
|
||||
frame.WindowSizeIncrement = BufferHelper.ReadUInt31(header.Payload, 0);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2GoAwayFrame ReadGoAwayFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#GOAWAY
|
||||
// str id error
|
||||
// | 0, 1, 2, 3 | 4, 5, 6, 7 | ...
|
||||
|
||||
HTTP2GoAwayFrame frame = new HTTP2GoAwayFrame(header);
|
||||
|
||||
frame.ReservedBit = BufferHelper.ReadBit(header.Payload[0], 0);
|
||||
frame.LastStreamId = BufferHelper.ReadUInt31(header.Payload, 0);
|
||||
frame.ErrorCode = BufferHelper.ReadUInt32(header.Payload, 4);
|
||||
|
||||
frame.AdditionalDebugDataLength = header.PayloadLength - 8;
|
||||
if (frame.AdditionalDebugDataLength > 0)
|
||||
{
|
||||
frame.AdditionalDebugData = BufferPool.Get(frame.AdditionalDebugDataLength, true);
|
||||
Array.Copy(header.Payload, 8, frame.AdditionalDebugData, 0, frame.AdditionalDebugDataLength);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2PingFrame ReadPingFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#PING
|
||||
|
||||
HTTP2PingFrame frame = new HTTP2PingFrame(header);
|
||||
|
||||
Array.Copy(header.Payload, 0, frame.OpaqueData, 0, frame.OpaqueDataLength);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2PushPromiseFrame ReadPush_PromiseFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#PUSH_PROMISE
|
||||
|
||||
HTTP2PushPromiseFrame frame = new HTTP2PushPromiseFrame(header);
|
||||
frame.HeaderBlockFragmentLength = header.PayloadLength - 4; // PromisedStreamId
|
||||
|
||||
bool isPadded = (frame.Flags & HTTP2PushPromiseFlags.PADDED) != 0;
|
||||
if (isPadded)
|
||||
{
|
||||
frame.PadLength = header.Payload[0];
|
||||
frame.HeaderBlockFragmentLength -= (uint)(1 + (frame.PadLength ?? 0));
|
||||
}
|
||||
|
||||
frame.ReservedBit = BufferHelper.ReadBit(header.Payload[1], 0);
|
||||
frame.PromisedStreamId = BufferHelper.ReadUInt31(header.Payload, 1);
|
||||
|
||||
frame.HeaderBlockFragmentIdx = (UInt32)(isPadded ? 5 : 4);
|
||||
frame.HeaderBlockFragment = header.Payload;
|
||||
header.Payload = null;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2RSTStreamFrame ReadRST_StreamFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#RST_STREAM
|
||||
|
||||
HTTP2RSTStreamFrame frame = new HTTP2RSTStreamFrame(header);
|
||||
frame.ErrorCode = BufferHelper.ReadUInt32(header.Payload, 0);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2PriorityFrame ReadPriorityFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#PRIORITY
|
||||
|
||||
if (header.PayloadLength != 5)
|
||||
{
|
||||
//throw FRAME_SIZE_ERROR
|
||||
}
|
||||
|
||||
HTTP2PriorityFrame frame = new HTTP2PriorityFrame(header);
|
||||
|
||||
frame.IsExclusive = BufferHelper.ReadBit(header.Payload[0], 0);
|
||||
frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, 0);
|
||||
frame.Weight = header.Payload[4];
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2HeadersFrame ReadHeadersFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#HEADERS
|
||||
|
||||
HTTP2HeadersFrame frame = new HTTP2HeadersFrame(header);
|
||||
frame.HeaderBlockFragmentLength = header.PayloadLength;
|
||||
|
||||
bool isPadded = (frame.Flags & HTTP2HeadersFlags.PADDED) != 0;
|
||||
bool isPriority = (frame.Flags & HTTP2HeadersFlags.PRIORITY) != 0;
|
||||
|
||||
int payloadIdx = 0;
|
||||
|
||||
if (isPadded)
|
||||
{
|
||||
frame.PadLength = header.Payload[payloadIdx++];
|
||||
|
||||
uint subLength = (uint)(1 + (frame.PadLength ?? 0));
|
||||
if (subLength <= frame.HeaderBlockFragmentLength)
|
||||
frame.HeaderBlockFragmentLength -= subLength;
|
||||
//else
|
||||
// throw PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
if (isPriority)
|
||||
{
|
||||
frame.IsExclusive = BufferHelper.ReadBit(header.Payload[payloadIdx], 0);
|
||||
frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, payloadIdx);
|
||||
payloadIdx += 4;
|
||||
frame.Weight = header.Payload[payloadIdx++];
|
||||
|
||||
uint subLength = 5;
|
||||
if (subLength <= frame.HeaderBlockFragmentLength)
|
||||
frame.HeaderBlockFragmentLength -= subLength;
|
||||
//else
|
||||
// throw PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
frame.HeaderBlockFragmentIdx = (UInt32)payloadIdx;
|
||||
frame.HeaderBlockFragment = header.Payload;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2DataFrame ReadDataFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#DATA
|
||||
|
||||
HTTP2DataFrame frame = new HTTP2DataFrame(header);
|
||||
|
||||
frame.DataLength = header.PayloadLength;
|
||||
|
||||
bool isPadded = (frame.Flags & HTTP2DataFlags.PADDED) != 0;
|
||||
if (isPadded)
|
||||
{
|
||||
frame.PadLength = header.Payload[0];
|
||||
|
||||
uint subLength = (uint)(1 + (frame.PadLength ?? 0));
|
||||
if (subLength <= frame.DataLength)
|
||||
frame.DataLength -= subLength;
|
||||
//else
|
||||
// throw PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
frame.DataIdx = (UInt32)(isPadded ? 1 : 0);
|
||||
frame.Data = header.Payload;
|
||||
header.Payload = null;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2AltSVCFrame ReadAltSvcFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
HTTP2AltSVCFrame frame = new HTTP2AltSVCFrame(header);
|
||||
|
||||
// Implement
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static void StreamRead(Stream stream, byte[] buffer, int offset, uint count)
|
||||
{
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
uint sumRead = 0;
|
||||
do
|
||||
{
|
||||
int readCount = (int)(count - sumRead);
|
||||
int streamReadCount = stream.Read(buffer, (int)(offset + sumRead), readCount);
|
||||
if (streamReadCount <= 0 && readCount > 0)
|
||||
throw new Exception("TCP Stream closed!");
|
||||
sumRead += (uint)streamReadCount;
|
||||
} while (sumRead < count);
|
||||
}
|
||||
|
||||
public static PooledBuffer HeaderAsBinary(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#FrameHeader
|
||||
|
||||
var buffer = BufferPool.Get(9, true);
|
||||
|
||||
BufferHelper.SetUInt24(buffer, 0, header.PayloadLength);
|
||||
buffer[3] = (byte)header.Type;
|
||||
buffer[4] = header.Flags;
|
||||
BufferHelper.SetUInt31(buffer, 5, header.StreamId);
|
||||
|
||||
return new PooledBuffer { Data = buffer, Length = 9 };
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload ReadHeader(Stream stream)
|
||||
{
|
||||
byte[] buffer = BufferPool.Get(9, true);
|
||||
|
||||
try
|
||||
{
|
||||
StreamRead(stream, buffer, 0, 9);
|
||||
}
|
||||
catch
|
||||
{
|
||||
BufferPool.Release(buffer);
|
||||
throw;
|
||||
}
|
||||
|
||||
HTTP2FrameHeaderAndPayload header = new HTTP2FrameHeaderAndPayload();
|
||||
|
||||
header.PayloadLength = BufferHelper.ReadUInt24(buffer, 0);
|
||||
header.Type = (HTTP2FrameTypes)buffer[3];
|
||||
header.Flags = buffer[4];
|
||||
header.StreamId = BufferHelper.ReadUInt31(buffer, 5);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
|
||||
header.Payload = BufferPool.Get(header.PayloadLength, true);
|
||||
|
||||
try
|
||||
{
|
||||
StreamRead(stream, header.Payload, 0, header.PayloadLength);
|
||||
}
|
||||
catch
|
||||
{
|
||||
BufferPool.Release(header.Payload);
|
||||
throw;
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
public static HTTP2SettingsFrame ReadSettings(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
HTTP2SettingsFrame frame = new HTTP2SettingsFrame(header);
|
||||
|
||||
if (header.PayloadLength > 0)
|
||||
{
|
||||
int kvpCount = (int)(header.PayloadLength / 6);
|
||||
|
||||
frame.Settings = new List<KeyValuePair<HTTP2Settings, uint>>(kvpCount);
|
||||
for (int i = 0; i < kvpCount; ++i)
|
||||
{
|
||||
HTTP2Settings key = (HTTP2Settings)BufferHelper.ReadUInt16(header.Payload, i * 6);
|
||||
UInt32 value = BufferHelper.ReadUInt32(header.Payload, (i * 6) + 2);
|
||||
|
||||
frame.Settings.Add(new KeyValuePair<HTTP2Settings, uint>(key, value));
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload CreateACKSettingsFrame()
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.SETTINGS;
|
||||
frame.Flags = (byte)HTTP2SettingsFlags.ACK;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload CreateSettingsFrame(List<KeyValuePair<HTTP2Settings, UInt32>> settings)
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.SETTINGS;
|
||||
frame.Flags = 0;
|
||||
frame.PayloadLength = (UInt32)settings.Count * 6;
|
||||
|
||||
frame.Payload = BufferPool.Get(frame.PayloadLength, true);
|
||||
|
||||
for (int i = 0; i < settings.Count; ++i)
|
||||
{
|
||||
BufferHelper.SetUInt16(frame.Payload, i * 6, (UInt16)settings[i].Key);
|
||||
BufferHelper.SetUInt32(frame.Payload, (i * 6) + 2, settings[i].Value);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload CreatePingFrame(HTTP2PingFlags flags = HTTP2PingFlags.None)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#PING
|
||||
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.PING;
|
||||
frame.Flags = (byte)flags;
|
||||
frame.StreamId = 0;
|
||||
frame.Payload = BufferPool.Get(8, true);
|
||||
frame.PayloadLength = 8;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload CreateWindowUpdateFrame(UInt32 streamId, UInt32 windowSizeIncrement)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE
|
||||
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.WINDOW_UPDATE;
|
||||
frame.Flags = 0;
|
||||
frame.StreamId = streamId;
|
||||
frame.Payload = BufferPool.Get(4, true);
|
||||
frame.PayloadLength = 4;
|
||||
|
||||
BufferHelper.SetBit(0, 0, 0);
|
||||
BufferHelper.SetUInt31(frame.Payload, 0, windowSizeIncrement);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload CreateGoAwayFrame(UInt32 lastStreamId, HTTP2ErrorCodes error)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#GOAWAY
|
||||
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.GOAWAY;
|
||||
frame.Flags = 0;
|
||||
frame.StreamId = 0;
|
||||
frame.Payload = BufferPool.Get(8, true);
|
||||
frame.PayloadLength = 8;
|
||||
|
||||
BufferHelper.SetUInt31(frame.Payload, 0, lastStreamId);
|
||||
BufferHelper.SetUInt31(frame.Payload, 4, (UInt32)error);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static HTTP2FrameHeaderAndPayload CreateRSTFrame(UInt32 streamId, HTTP2ErrorCodes errorCode)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#RST_STREAM
|
||||
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.RST_STREAM;
|
||||
frame.Flags = 0;
|
||||
frame.StreamId = streamId;
|
||||
frame.Payload = BufferPool.Get(4, true);
|
||||
frame.PayloadLength = 4;
|
||||
|
||||
BufferHelper.SetUInt32(frame.Payload, 0, (UInt32)errorCode);
|
||||
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,406 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#iana-frames
|
||||
public enum HTTP2FrameTypes : byte
|
||||
{
|
||||
DATA = 0x00,
|
||||
HEADERS = 0x01,
|
||||
PRIORITY = 0x02,
|
||||
RST_STREAM = 0x03,
|
||||
SETTINGS = 0x04,
|
||||
PUSH_PROMISE = 0x05,
|
||||
PING = 0x06,
|
||||
GOAWAY = 0x07,
|
||||
WINDOW_UPDATE = 0x08,
|
||||
CONTINUATION = 0x09,
|
||||
|
||||
// https://tools.ietf.org/html/rfc7838#section-4
|
||||
ALT_SVC = 0x0A
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HTTP2DataFlags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
END_STREAM = 0x01,
|
||||
PADDED = 0x08,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HTTP2HeadersFlags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
END_STREAM = 0x01,
|
||||
END_HEADERS = 0x04,
|
||||
PADDED = 0x08,
|
||||
PRIORITY = 0x20,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HTTP2SettingsFlags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
ACK = 0x01,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HTTP2PushPromiseFlags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
END_HEADERS = 0x04,
|
||||
PADDED = 0x08,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HTTP2PingFlags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
ACK = 0x01,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HTTP2ContinuationFlags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
END_HEADERS = 0x04,
|
||||
}
|
||||
|
||||
public struct HTTP2FrameHeaderAndPayload
|
||||
{
|
||||
public UInt32 PayloadLength;
|
||||
public HTTP2FrameTypes Type;
|
||||
public byte Flags;
|
||||
public UInt32 StreamId;
|
||||
public byte[] Payload;
|
||||
|
||||
public UInt32 PayloadOffset;
|
||||
public bool DontUseMemPool;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2FrameHeaderAndPayload Length: {0}, Type: {1}, Flags: {2}, StreamId: {3}, PayloadOffset: {4}, DontUseMemPool: {5}, Payload: {6}]",
|
||||
this.PayloadLength, this.Type, this.Flags.ToBinaryStr(), this.StreamId, this.PayloadOffset, this.DontUseMemPool,
|
||||
this.Payload == null ? BufferSegment.Empty : new BufferSegment(this.Payload, (int)this.PayloadOffset, (int)this.PayloadLength));
|
||||
}
|
||||
|
||||
public string PayloadAsHex()
|
||||
{
|
||||
System.Text.StringBuilder sb = PlatformSupport.Text.StringBuilderPool.Get((int)this.PayloadLength + 2);
|
||||
sb.Append("[");
|
||||
if (this.Payload != null && this.PayloadLength > 0)
|
||||
{
|
||||
uint idx = this.PayloadOffset;
|
||||
sb.Append(this.Payload[idx++]);
|
||||
for (int i = 1; i < this.PayloadLength; i++)
|
||||
sb.AppendFormat(", {0:X2}", this.Payload[idx++]);
|
||||
}
|
||||
sb.Append("]");
|
||||
|
||||
return PlatformSupport.Text.StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2SettingsFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2SettingsFlags Flags { get { return (HTTP2SettingsFlags)this.Header.Flags; } }
|
||||
public List<KeyValuePair<HTTP2Settings, UInt32>> Settings;
|
||||
|
||||
public HTTP2SettingsFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.Settings = null;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string settings = null;
|
||||
if (this.Settings != null)
|
||||
{
|
||||
System.Text.StringBuilder sb = PlatformSupport.Text.StringBuilderPool.Get(this.Settings.Count + 2);
|
||||
|
||||
sb.Append("[");
|
||||
foreach (var kvp in this.Settings)
|
||||
sb.AppendFormat("[{0}: {1}]", kvp.Key, kvp.Value);
|
||||
sb.Append("]");
|
||||
|
||||
settings = PlatformSupport.Text.StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
|
||||
return string.Format("[HTTP2SettingsFrame Header: {0}, Flags: {1}, Settings: {2}]", this.Header.ToString(), this.Flags, settings ?? "Empty");
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2DataFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2DataFlags Flags { get { return (HTTP2DataFlags)this.Header.Flags; } }
|
||||
|
||||
public byte? PadLength;
|
||||
public UInt32 DataIdx;
|
||||
public byte[] Data;
|
||||
public uint DataLength;
|
||||
|
||||
public HTTP2DataFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.PadLength = null;
|
||||
this.DataIdx = 0;
|
||||
this.Data = null;
|
||||
this.DataLength = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2DataFrame Header: {0}, Flags: {1}, PadLength: {2}, DataLength: {3}]",
|
||||
this.Header.ToString(),
|
||||
this.Flags,
|
||||
this.PadLength == null ? ":Empty" : this.PadLength.Value.ToString(),
|
||||
this.DataLength);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2HeadersFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2HeadersFlags Flags { get { return (HTTP2HeadersFlags)this.Header.Flags; } }
|
||||
|
||||
public byte? PadLength;
|
||||
public byte? IsExclusive;
|
||||
public UInt32? StreamDependency;
|
||||
public byte? Weight;
|
||||
public UInt32 HeaderBlockFragmentIdx;
|
||||
public byte[] HeaderBlockFragment;
|
||||
public UInt32 HeaderBlockFragmentLength;
|
||||
|
||||
public HTTP2HeadersFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.PadLength = null;
|
||||
this.IsExclusive = null;
|
||||
this.StreamDependency = null;
|
||||
this.Weight = null;
|
||||
this.HeaderBlockFragmentIdx = 0;
|
||||
this.HeaderBlockFragment = null;
|
||||
this.HeaderBlockFragmentLength = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2HeadersFrame Header: {0}, Flags: {1}, PadLength: {2}, IsExclusive: {3}, StreamDependency: {4}, Weight: {5}, HeaderBlockFragmentLength: {6}]",
|
||||
this.Header.ToString(),
|
||||
this.Flags,
|
||||
this.PadLength == null ? ":Empty" : this.PadLength.Value.ToString(),
|
||||
this.IsExclusive == null ? "Empty" : this.IsExclusive.Value.ToString(),
|
||||
this.StreamDependency == null ? "Empty" : this.StreamDependency.Value.ToString(),
|
||||
this.Weight == null ? "Empty" : this.Weight.Value.ToString(),
|
||||
this.HeaderBlockFragmentLength);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2PriorityFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
|
||||
public byte IsExclusive;
|
||||
public UInt32 StreamDependency;
|
||||
public byte Weight;
|
||||
|
||||
public HTTP2PriorityFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.IsExclusive = 0;
|
||||
this.StreamDependency = 0;
|
||||
this.Weight = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2PriorityFrame Header: {0}, IsExclusive: {1}, StreamDependency: {2}, Weight: {3}]",
|
||||
this.Header.ToString(), this.IsExclusive, this.StreamDependency, this.Weight);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2RSTStreamFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
|
||||
public UInt32 ErrorCode;
|
||||
public HTTP2ErrorCodes Error { get { return (HTTP2ErrorCodes)this.ErrorCode; } }
|
||||
|
||||
public HTTP2RSTStreamFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.ErrorCode = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2RST_StreamFrame Header: {0}, Error: {1}({2})]", this.Header.ToString(), this.Error, this.ErrorCode);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2PushPromiseFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2PushPromiseFlags Flags { get { return (HTTP2PushPromiseFlags)this.Header.Flags; } }
|
||||
|
||||
public byte? PadLength;
|
||||
public byte ReservedBit;
|
||||
public UInt32 PromisedStreamId;
|
||||
public UInt32 HeaderBlockFragmentIdx;
|
||||
public byte[] HeaderBlockFragment;
|
||||
public UInt32 HeaderBlockFragmentLength;
|
||||
|
||||
public HTTP2PushPromiseFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.PadLength = null;
|
||||
this.ReservedBit = 0;
|
||||
this.PromisedStreamId = 0;
|
||||
this.HeaderBlockFragmentIdx = 0;
|
||||
this.HeaderBlockFragment = null;
|
||||
this.HeaderBlockFragmentLength = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2Push_PromiseFrame Header: {0}, Flags: {1}, PadLength: {2}, ReservedBit: {3}, PromisedStreamId: {4}, HeaderBlockFragmentLength: {5}]",
|
||||
this.Header.ToString(),
|
||||
this.Flags,
|
||||
this.PadLength == null ? "Empty" : this.PadLength.Value.ToString(),
|
||||
this.ReservedBit,
|
||||
this.PromisedStreamId,
|
||||
this.HeaderBlockFragmentLength);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2PingFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2PingFlags Flags { get { return (HTTP2PingFlags)this.Header.Flags; } }
|
||||
|
||||
public readonly byte[] OpaqueData;
|
||||
public readonly byte OpaqueDataLength;
|
||||
|
||||
public HTTP2PingFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.OpaqueData = BufferPool.Get(8, true);
|
||||
this.OpaqueDataLength = 8;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2PingFrame Header: {0}, Flags: {1}, OpaqueData: {2}]",
|
||||
this.Header.ToString(),
|
||||
this.Flags,
|
||||
SecureProtocol.Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(this.OpaqueData, 0, this.OpaqueDataLength));
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2GoAwayFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2ErrorCodes Error { get { return (HTTP2ErrorCodes)this.ErrorCode; } }
|
||||
|
||||
public byte ReservedBit;
|
||||
public UInt32 LastStreamId;
|
||||
public UInt32 ErrorCode;
|
||||
public byte[] AdditionalDebugData;
|
||||
public UInt32 AdditionalDebugDataLength;
|
||||
|
||||
public HTTP2GoAwayFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.ReservedBit = 0;
|
||||
this.LastStreamId = 0;
|
||||
this.ErrorCode = 0;
|
||||
this.AdditionalDebugData = null;
|
||||
this.AdditionalDebugDataLength = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2GoAwayFrame Header: {0}, ReservedBit: {1}, LastStreamId: {2}, Error: {3}({4}), AdditionalDebugData({5}): {6}]",
|
||||
this.Header.ToString(),
|
||||
this.ReservedBit,
|
||||
this.LastStreamId,
|
||||
this.Error,
|
||||
this.ErrorCode,
|
||||
this.AdditionalDebugDataLength,
|
||||
this.AdditionalDebugData == null ? "Empty" : SecureProtocol.Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(this.AdditionalDebugData, 0, (int)this.AdditionalDebugDataLength));
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2WindowUpdateFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
|
||||
public byte ReservedBit;
|
||||
public UInt32 WindowSizeIncrement;
|
||||
|
||||
public HTTP2WindowUpdateFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.ReservedBit = 0;
|
||||
this.WindowSizeIncrement = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2WindowUpdateFrame Header: {0}, ReservedBit: {1}, WindowSizeIncrement: {2}]",
|
||||
this.Header.ToString(), this.ReservedBit, this.WindowSizeIncrement);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HTTP2ContinuationFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
public HTTP2ContinuationFlags Flags { get { return (HTTP2ContinuationFlags)this.Header.Flags; } }
|
||||
|
||||
public byte[] HeaderBlockFragment;
|
||||
public UInt32 HeaderBlockFragmentLength { get { return this.Header.PayloadLength; } }
|
||||
|
||||
public HTTP2ContinuationFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.HeaderBlockFragment = null;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HTTP2ContinuationFrame Header: {0}, Flags: {1}, HeaderBlockFragmentLength: {2}]",
|
||||
this.Header.ToString(),
|
||||
this.Flags,
|
||||
this.HeaderBlockFragmentLength);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://tools.ietf.org/html/rfc7838#section-4
|
||||
/// </summary>
|
||||
public struct HTTP2AltSVCFrame
|
||||
{
|
||||
public readonly HTTP2FrameHeaderAndPayload Header;
|
||||
|
||||
public string Origin;
|
||||
public string AltSvcFieldValue;
|
||||
|
||||
public HTTP2AltSVCFrame(HTTP2FrameHeaderAndPayload header)
|
||||
{
|
||||
this.Header = header;
|
||||
this.Origin = null;
|
||||
this.AltSvcFieldValue = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,671 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.Core;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using BestHTTP.Logger;
|
||||
using BestHTTP.PlatformSupport.Threading;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
public sealed class HTTP2Handler : IHTTPRequestHandler
|
||||
{
|
||||
public bool HasCustomRequestProcessor { get { return true; } }
|
||||
|
||||
public KeepAliveHeader KeepAlive { get { return null; } }
|
||||
|
||||
public bool CanProcessMultiple { get { return this.goAwaySentAt == DateTime.MaxValue && this.isRunning; } }
|
||||
|
||||
// Connection preface starts with the string PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n).
|
||||
private static readonly byte[] MAGIC = new byte[24] { 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a };
|
||||
public const UInt32 MaxValueFor31Bits = 0xFFFFFFFF >> 1;
|
||||
|
||||
public double Latency { get; private set; }
|
||||
|
||||
public HTTP2SettingsManager settings;
|
||||
public HPACKEncoder HPACKEncoder;
|
||||
|
||||
public LoggingContext Context { get; private set; }
|
||||
|
||||
private DateTime lastPingSent = DateTime.MinValue;
|
||||
private TimeSpan pingFrequency = TimeSpan.MaxValue; // going to be overridden in RunHandler
|
||||
private int waitingForPingAck = 0;
|
||||
|
||||
public static int RTTBufferCapacity = 5;
|
||||
private CircularBuffer<double> rtts = new CircularBuffer<double>(RTTBufferCapacity);
|
||||
|
||||
private volatile bool isRunning;
|
||||
|
||||
private AutoResetEvent newFrameSignal = new AutoResetEvent(false);
|
||||
|
||||
private ConcurrentQueue<HTTPRequest> requestQueue = new ConcurrentQueue<HTTPRequest>();
|
||||
|
||||
private List<HTTP2Stream> clientInitiatedStreams = new List<HTTP2Stream>();
|
||||
|
||||
private ConcurrentQueue<HTTP2FrameHeaderAndPayload> newFrames = new ConcurrentQueue<HTTP2FrameHeaderAndPayload>();
|
||||
|
||||
private List<HTTP2FrameHeaderAndPayload> outgoingFrames = new List<HTTP2FrameHeaderAndPayload>();
|
||||
|
||||
private UInt32 remoteWindow;
|
||||
private DateTime lastInteraction;
|
||||
private DateTime goAwaySentAt = DateTime.MaxValue;
|
||||
|
||||
private HTTPConnection conn;
|
||||
private int threadExitCount;
|
||||
|
||||
private TimeSpan MaxGoAwayWaitTime { get { return this.goAwaySentAt == DateTime.MaxValue ? TimeSpan.MaxValue : TimeSpan.FromMilliseconds(Math.Max(this.Latency * 2.5, 1500)); } }
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#StreamIdentifiers
|
||||
// Streams initiated by a client MUST use odd-numbered stream identifiers
|
||||
// With an initial value of -1, the first client initiated stream's id going to be 1.
|
||||
private long LastStreamId = -1;
|
||||
|
||||
public HTTP2Handler(HTTPConnection conn)
|
||||
{
|
||||
this.Context = new LoggingContext(this);
|
||||
|
||||
this.conn = conn;
|
||||
this.isRunning = true;
|
||||
|
||||
this.settings = new HTTP2SettingsManager(this);
|
||||
|
||||
Process(this.conn.CurrentRequest);
|
||||
}
|
||||
|
||||
public void Process(HTTPRequest request)
|
||||
{
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "Process request called", this.Context, request.Context);
|
||||
|
||||
request.QueuedAt = DateTime.MinValue;
|
||||
request.ProcessingStarted = this.lastInteraction = DateTime.UtcNow;
|
||||
|
||||
this.requestQueue.Enqueue(request);
|
||||
|
||||
// Wee might added the request to a dead queue, signaling would be pointless.
|
||||
// When the ConnectionEventHelper processes the Close state-change event
|
||||
// requests in the queue going to be resent. (We should avoid resending the request just right now,
|
||||
// as it might still select this connection/handler resulting in a infinite loop.)
|
||||
if (Volatile.Read(ref this.threadExitCount) == 0)
|
||||
this.newFrameSignal.Set();
|
||||
}
|
||||
|
||||
public void SignalRunnerThread()
|
||||
{
|
||||
this.newFrameSignal?.Set();
|
||||
}
|
||||
|
||||
public void RunHandler()
|
||||
{
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "Processing thread up and running!", this.Context);
|
||||
|
||||
ThreadedRunner.SetThreadName("BestHTTP.HTTP2 Process");
|
||||
|
||||
PlatformSupport.Threading.ThreadedRunner.RunLongLiving(ReadThread);
|
||||
|
||||
try
|
||||
{
|
||||
bool atLeastOneStreamHasAFrameToSend = true;
|
||||
|
||||
this.HPACKEncoder = new HPACKEncoder(this, this.settings);
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#InitialWindowSize
|
||||
// The connection flow-control window is also 65,535 octets.
|
||||
this.remoteWindow = this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
|
||||
|
||||
// we want to pack as many data as we can in one tcp segment, but setting the buffer's size too high
|
||||
// we might keep data too long and send them in bursts instead of in a steady stream.
|
||||
// Keeping it too low might result in a full tcp segment and one with very low payload
|
||||
// Is it possible that one full tcp segment sized buffer would be the best, or multiple of it.
|
||||
// It would keep the network busy without any fragments. The ethernet layer has a maximum of 1500 bytes,
|
||||
// but there's two layers of 20 byte headers each, so as a theoretical maximum it's 1500-20-20 bytes.
|
||||
// On the other hand, if the buffer is small (1-2), that means that for larger data, we have to do a lot
|
||||
// of system calls, in that case a larger buffer might be better. Still, if we are not cpu bound,
|
||||
// a well saturated network might serve us better.
|
||||
using (WriteOnlyBufferedStream bufferedStream = new WriteOnlyBufferedStream(this.conn.connector.Stream, 1024 * 1024 /*1500 - 20 - 20*/))
|
||||
{
|
||||
// The client connection preface starts with a sequence of 24 octets
|
||||
bufferedStream.Write(MAGIC, 0, MAGIC.Length);
|
||||
|
||||
// This sequence MUST be followed by a SETTINGS frame (Section 6.5), which MAY be empty.
|
||||
// The client sends the client connection preface immediately upon receipt of a
|
||||
// 101 (Switching Protocols) response (indicating a successful upgrade)
|
||||
// or as the first application data octets of a TLS connection
|
||||
|
||||
// Set streams' initial window size to its maximum.
|
||||
this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] = HTTPManager.HTTP2Settings.InitialStreamWindowSize;
|
||||
this.settings.InitiatedMySettings[HTTP2Settings.MAX_CONCURRENT_STREAMS] = HTTPManager.HTTP2Settings.MaxConcurrentStreams;
|
||||
this.settings.InitiatedMySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] = (uint)(HTTPManager.HTTP2Settings.EnableConnectProtocol ? 1 : 0);
|
||||
this.settings.InitiatedMySettings[HTTP2Settings.ENABLE_PUSH] = 0;
|
||||
this.settings.SendChanges(this.outgoingFrames);
|
||||
this.settings.RemoteSettings.OnSettingChangedEvent += OnRemoteSettingChanged;
|
||||
|
||||
// The default window size for the whole connection is 65535 bytes,
|
||||
// but we want to set it to the maximum possible value.
|
||||
Int64 initialConnectionWindowSize = HTTPManager.HTTP2Settings.InitialConnectionWindowSize;
|
||||
|
||||
// yandex.ru returns with an FLOW_CONTROL_ERROR (3) error when the plugin tries to set the connection window to 2^31 - 1
|
||||
// and works only with a maximum value of 2^31 - 10Mib (10 * 1024 * 1024).
|
||||
if (initialConnectionWindowSize == HTTP2Handler.MaxValueFor31Bits)
|
||||
initialConnectionWindowSize -= 10 * 1024 * 1024;
|
||||
|
||||
Int64 diff = initialConnectionWindowSize - 65535;
|
||||
if (diff > 0)
|
||||
this.outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(0, (UInt32)diff));
|
||||
|
||||
this.pingFrequency = HTTPManager.HTTP2Settings.PingFrequency;
|
||||
|
||||
while (this.isRunning)
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
|
||||
if (!atLeastOneStreamHasAFrameToSend)
|
||||
{
|
||||
// buffered stream will call flush automatically if its internal buffer is full.
|
||||
// But we have to make it sure that we flush remaining data before we go to sleep.
|
||||
bufferedStream.Flush();
|
||||
|
||||
// Wait until we have to send the next ping, OR a new frame is received on the read thread.
|
||||
// lastPingSent Now lastPingSent+frequency lastPingSent+Ping timeout
|
||||
//----|---------------------|---------------|----------------------|----------------------|------------|
|
||||
// lastInteraction lastInteraction + MaxIdleTime
|
||||
|
||||
var sendPingAt = this.lastPingSent + this.pingFrequency;
|
||||
var timeoutAt = this.waitingForPingAck != 0 ? this.lastPingSent + HTTPManager.HTTP2Settings.Timeout : DateTime.MaxValue;
|
||||
var nextPingInteraction = sendPingAt < timeoutAt ? sendPingAt : timeoutAt;
|
||||
|
||||
var disconnectByIdleAt = this.lastInteraction + HTTPManager.HTTP2Settings.MaxIdleTime;
|
||||
|
||||
var nextDueClientInteractionAt = nextPingInteraction < disconnectByIdleAt ? nextPingInteraction : disconnectByIdleAt;
|
||||
int wait = (int)(nextDueClientInteractionAt - now).TotalMilliseconds;
|
||||
|
||||
wait = (int)Math.Min(wait, this.MaxGoAwayWaitTime.TotalMilliseconds);
|
||||
|
||||
TimeSpan nextStreamInteraction = TimeSpan.MaxValue;
|
||||
for (int i = 0; i < this.clientInitiatedStreams.Count; i++)
|
||||
{
|
||||
var streamInteraction = this.clientInitiatedStreams[i].NextInteraction;
|
||||
if (streamInteraction < nextStreamInteraction)
|
||||
nextStreamInteraction = streamInteraction;
|
||||
}
|
||||
|
||||
wait = (int)Math.Min(wait, nextStreamInteraction.TotalMilliseconds);
|
||||
|
||||
if (wait >= 1)
|
||||
{
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Information("HTTP2Handler", string.Format("Sleeping for {0:N0}ms", wait), this.Context);
|
||||
this.newFrameSignal.WaitOne(wait);
|
||||
|
||||
now = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't send a new ping until a pong isn't received for the last one
|
||||
if (now - this.lastPingSent >= this.pingFrequency && Interlocked.CompareExchange(ref this.waitingForPingAck, 1, 0) == 0)
|
||||
{
|
||||
this.lastPingSent = now;
|
||||
|
||||
var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.None);
|
||||
BufferHelper.SetLong(frame.Payload, 0, now.Ticks);
|
||||
|
||||
this.outgoingFrames.Add(frame);
|
||||
}
|
||||
|
||||
// If no pong received in a (configurable) reasonable time, treat the connection broken
|
||||
if (this.waitingForPingAck != 0 && now - this.lastPingSent >= HTTPManager.HTTP2Settings.Timeout)
|
||||
throw new TimeoutException("Ping ACK isn't received in time!");
|
||||
|
||||
// Process received frames
|
||||
HTTP2FrameHeaderAndPayload header;
|
||||
while (this.newFrames.TryDequeue(out header))
|
||||
{
|
||||
if (header.StreamId > 0)
|
||||
{
|
||||
HTTP2Stream http2Stream = FindStreamById(header.StreamId);
|
||||
|
||||
// Add frame to the stream, so it can process it when its Process function is called
|
||||
if (http2Stream != null)
|
||||
{
|
||||
http2Stream.AddFrame(header, this.outgoingFrames);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Error? It's possible that we closed and removed the stream while the server was in the middle of sending frames
|
||||
if (HTTPManager.Logger.Level == Loglevels.All)
|
||||
HTTPManager.Logger.Warning("HTTP2Handler", string.Format("No stream found for id: {0}! Can't deliver frame: {1}", header.StreamId, header), this.Context, http2Stream.Context);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (header.Type)
|
||||
{
|
||||
case HTTP2FrameTypes.SETTINGS:
|
||||
this.settings.Process(header, this.outgoingFrames);
|
||||
|
||||
PluginEventHelper.EnqueuePluginEvent(
|
||||
new PluginEventInfo(PluginEvents.HTTP2ConnectProtocol,
|
||||
new HTTP2ConnectProtocolInfo(this.conn.LastProcessedUri.Host,
|
||||
this.settings.MySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] == 1 && this.settings.RemoteSettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] == 1)));
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.PING:
|
||||
var pingFrame = HTTP2FrameHelper.ReadPingFrame(header);
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#PING
|
||||
// if it wasn't an ack for our ping, we have to send one
|
||||
if ((pingFrame.Flags & HTTP2PingFlags.ACK) == 0)
|
||||
{
|
||||
var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.ACK);
|
||||
Array.Copy(pingFrame.OpaqueData, 0, frame.Payload, 0, pingFrame.OpaqueDataLength);
|
||||
|
||||
this.outgoingFrames.Add(frame);
|
||||
}
|
||||
|
||||
BufferPool.Release(pingFrame.OpaqueData);
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.WINDOW_UPDATE:
|
||||
var windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(header);
|
||||
this.remoteWindow += windowUpdateFrame.WindowSizeIncrement;
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.GOAWAY:
|
||||
// parse the frame, so we can print out detailed information
|
||||
HTTP2GoAwayFrame goAwayFrame = HTTP2FrameHelper.ReadGoAwayFrame(header);
|
||||
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "Received GOAWAY frame: " + goAwayFrame.ToString(), this.Context);
|
||||
|
||||
string msg = string.Format("Server closing the connection! Error code: {0} ({1}) Additonal Debug Data: {2}", goAwayFrame.Error, goAwayFrame.ErrorCode, new BufferSegment(goAwayFrame.AdditionalDebugData, 0, (int)goAwayFrame.AdditionalDebugDataLength));
|
||||
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
|
||||
this.clientInitiatedStreams[i].Abort(msg);
|
||||
this.clientInitiatedStreams.Clear();
|
||||
|
||||
// set the running flag to false, so the thread can exit
|
||||
this.isRunning = false;
|
||||
|
||||
BufferPool.Release(goAwayFrame.AdditionalDebugData);
|
||||
|
||||
//this.conn.State = HTTPConnectionStates.Closed;
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.ALT_SVC:
|
||||
//HTTP2AltSVCFrame altSvcFrame = HTTP2FrameHelper.ReadAltSvcFrame(header);
|
||||
|
||||
// Implement
|
||||
//HTTPManager.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.AltSvcHeader, new AltSvcEventInfo(altSvcFrame.Origin, ))
|
||||
break;
|
||||
}
|
||||
|
||||
if (header.Payload != null)
|
||||
BufferPool.Release(header.Payload);
|
||||
}
|
||||
}
|
||||
|
||||
UInt32 maxConcurrentStreams = Math.Min(HTTPManager.HTTP2Settings.MaxConcurrentStreams, this.settings.RemoteSettings[HTTP2Settings.MAX_CONCURRENT_STREAMS]);
|
||||
|
||||
// pre-test stream count to lock only when truly needed.
|
||||
if (this.clientInitiatedStreams.Count < maxConcurrentStreams && this.isRunning)
|
||||
{
|
||||
// grab requests from queue
|
||||
HTTPRequest request;
|
||||
while (this.clientInitiatedStreams.Count < maxConcurrentStreams && this.requestQueue.TryDequeue(out request))
|
||||
{
|
||||
HTTP2Stream newStream = null;
|
||||
#if !BESTHTTP_DISABLE_WEBSOCKET
|
||||
if (request.Tag is WebSocket.OverHTTP2)
|
||||
{
|
||||
newStream = new HTTP2WebSocketStream((UInt32)Interlocked.Add(ref LastStreamId, 2), this, this.settings, this.HPACKEncoder);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
newStream = new HTTP2Stream((UInt32)Interlocked.Add(ref LastStreamId, 2), this, this.settings, this.HPACKEncoder);
|
||||
}
|
||||
|
||||
newStream.Assign(request);
|
||||
this.clientInitiatedStreams.Add(newStream);
|
||||
}
|
||||
}
|
||||
|
||||
// send any settings changes
|
||||
this.settings.SendChanges(this.outgoingFrames);
|
||||
|
||||
atLeastOneStreamHasAFrameToSend = false;
|
||||
|
||||
// process other streams
|
||||
// Room for improvement Streams should be processed by their priority!
|
||||
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
|
||||
{
|
||||
var stream = this.clientInitiatedStreams[i];
|
||||
stream.Process(this.outgoingFrames);
|
||||
|
||||
// remove closed, empty streams (not enough to check the closed flag, a closed stream still can contain frames to send)
|
||||
if (stream.State == HTTP2StreamStates.Closed && !stream.HasFrameToSend)
|
||||
{
|
||||
this.clientInitiatedStreams.RemoveAt(i--);
|
||||
stream.Removed();
|
||||
}
|
||||
|
||||
atLeastOneStreamHasAFrameToSend |= stream.HasFrameToSend;
|
||||
|
||||
this.lastInteraction = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
// If we encounter a data frame that too large for the current remote window, we have to stop
|
||||
// sending all data frames as we could send smaller data frames before the large ones.
|
||||
// Room for improvement: An improvement would be here to stop data frame sending per-stream.
|
||||
bool haltDataSending = false;
|
||||
|
||||
if (this.ShutdownType == ShutdownTypes.Running && now - this.lastInteraction >= HTTPManager.HTTP2Settings.MaxIdleTime)
|
||||
{
|
||||
this.lastInteraction = DateTime.UtcNow;
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "Reached idle time, sending GoAway frame!", this.Context);
|
||||
this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR));
|
||||
this.goAwaySentAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#GOAWAY
|
||||
// Endpoints SHOULD always send a GOAWAY frame before closing a connection so that the remote peer can know whether a stream has been partially processed or not.
|
||||
if (this.ShutdownType == ShutdownTypes.Gentle)
|
||||
{
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "Connection abort requested, sending GoAway frame!", this.Context);
|
||||
|
||||
this.outgoingFrames.Clear();
|
||||
this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR));
|
||||
this.goAwaySentAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if (this.isRunning && now - goAwaySentAt >= this.MaxGoAwayWaitTime)
|
||||
{
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "No GoAway frame received back. Really quitting now!", this.Context);
|
||||
this.isRunning = false;
|
||||
|
||||
//conn.State = HTTPConnectionStates.Closed;
|
||||
}
|
||||
|
||||
uint streamWindowUpdates = 0;
|
||||
|
||||
// Go through all the collected frames and send them.
|
||||
for (int i = 0; i < this.outgoingFrames.Count; ++i)
|
||||
{
|
||||
var frame = this.outgoingFrames[i];
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.All && frame.Type != HTTP2FrameTypes.DATA /*&& frame.Type != HTTP2FrameTypes.PING*/)
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "Sending frame: " + frame.ToString(), this.Context);
|
||||
|
||||
// post process frames
|
||||
switch (frame.Type)
|
||||
{
|
||||
case HTTP2FrameTypes.DATA:
|
||||
if (haltDataSending)
|
||||
continue;
|
||||
|
||||
// if the tracked remoteWindow is smaller than the frame's payload, we stop sending
|
||||
// data frames until we receive window-update frames
|
||||
if (frame.PayloadLength > this.remoteWindow)
|
||||
{
|
||||
haltDataSending = true;
|
||||
HTTPManager.Logger.Warning("HTTP2Handler", string.Format("Data sending halted for this round. Remote Window: {0:N0}, frame: {1}", this.remoteWindow, frame.ToString()), this.Context);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.WINDOW_UPDATE:
|
||||
if (frame.StreamId > 0)
|
||||
streamWindowUpdates += BufferHelper.ReadUInt31(frame.Payload, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
this.outgoingFrames.RemoveAt(i--);
|
||||
|
||||
using (var buffer = HTTP2FrameHelper.HeaderAsBinary(frame))
|
||||
bufferedStream.Write(buffer.Data, 0, buffer.Length);
|
||||
|
||||
if (frame.PayloadLength > 0)
|
||||
{
|
||||
bufferedStream.Write(frame.Payload, (int)frame.PayloadOffset, (int)frame.PayloadLength);
|
||||
|
||||
if (!frame.DontUseMemPool)
|
||||
BufferPool.Release(frame.Payload);
|
||||
}
|
||||
|
||||
if (frame.Type == HTTP2FrameTypes.DATA)
|
||||
this.remoteWindow -= frame.PayloadLength;
|
||||
}
|
||||
|
||||
if (streamWindowUpdates > 0)
|
||||
{
|
||||
var frame = HTTP2FrameHelper.CreateWindowUpdateFrame(0, streamWindowUpdates);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "Sending frame: " + frame.ToString(), this.Context);
|
||||
|
||||
using (var buffer = HTTP2FrameHelper.HeaderAsBinary(frame))
|
||||
bufferedStream.Write(buffer.Data, 0, buffer.Length);
|
||||
|
||||
bufferedStream.Write(frame.Payload, (int)frame.PayloadOffset, (int)frame.PayloadLength);
|
||||
|
||||
if (!frame.DontUseMemPool)
|
||||
BufferPool.Release(frame.Payload);
|
||||
}
|
||||
|
||||
} // while (this.isRunning)
|
||||
|
||||
bufferedStream.Flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log out the exception if it's a non-expected one.
|
||||
if (this.ShutdownType == ShutdownTypes.Running && this.goAwaySentAt == DateTime.MaxValue && !HTTPManager.IsQuitting)
|
||||
HTTPManager.Logger.Exception("HTTP2Handler", "Sender thread", ex, this.Context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
TryToCleanup();
|
||||
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing - cleaning up remaining request...", this.Context);
|
||||
|
||||
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
|
||||
this.clientInitiatedStreams[i].Abort("Connection closed unexpectedly");
|
||||
this.clientInitiatedStreams.Clear();
|
||||
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing", this.Context);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (this.conn != null && this.conn.connector != null)
|
||||
{
|
||||
// Works in the new runtime
|
||||
if (this.conn.connector.TopmostStream != null)
|
||||
using (this.conn.connector.TopmostStream) { }
|
||||
|
||||
// Works in the old runtime
|
||||
if (this.conn.connector.Stream != null)
|
||||
using (this.conn.connector.Stream) { }
|
||||
}
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
|
||||
private void OnRemoteSettingChanged(HTTP2SettingsRegistry registry, HTTP2Settings setting, uint oldValue, uint newValue)
|
||||
{
|
||||
switch(setting)
|
||||
{
|
||||
case HTTP2Settings.INITIAL_WINDOW_SIZE:
|
||||
this.remoteWindow = newValue - (oldValue - this.remoteWindow);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadThread()
|
||||
{
|
||||
try
|
||||
{
|
||||
ThreadedRunner.SetThreadName("BestHTTP.HTTP2 Read");
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "Reader thread up and running!", this.Context);
|
||||
|
||||
while (this.isRunning)
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload header = HTTP2FrameHelper.ReadHeader(this.conn.connector.Stream);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information && header.Type != HTTP2FrameTypes.DATA /*&& header.Type != HTTP2FrameTypes.PING*/)
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "New frame received: " + header.ToString(), this.Context);
|
||||
|
||||
// Add the new frame to the queue. Processing it on the write thread gives us the advantage that
|
||||
// we don't have to deal with too much locking.
|
||||
this.newFrames.Enqueue(header);
|
||||
|
||||
// ping write thread to process the new frame
|
||||
this.newFrameSignal.Set();
|
||||
|
||||
switch (header.Type)
|
||||
{
|
||||
// Handle pongs on the read thread, so no additional latency is added to the rtt calculation.
|
||||
case HTTP2FrameTypes.PING:
|
||||
var pingFrame = HTTP2FrameHelper.ReadPingFrame(header);
|
||||
|
||||
if ((pingFrame.Flags & HTTP2PingFlags.ACK) != 0)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref this.waitingForPingAck, 0, 1) == 0)
|
||||
break; // waitingForPingAck was 0 == aren't expecting a ping ack!
|
||||
|
||||
// it was an ack, payload must contain what we sent
|
||||
|
||||
var ticks = BufferHelper.ReadLong(pingFrame.OpaqueData, 0);
|
||||
|
||||
// the difference between the current time and the time when the ping message is sent
|
||||
TimeSpan diff = TimeSpan.FromTicks(DateTime.UtcNow.Ticks - ticks);
|
||||
|
||||
// add it to the buffer
|
||||
this.rtts.Add(diff.TotalMilliseconds);
|
||||
|
||||
// and calculate the new latency
|
||||
this.Latency = CalculateLatency();
|
||||
|
||||
HTTPManager.Logger.Verbose("HTTP2Handler", string.Format("Latency: {0:F2}ms, RTT buffer: {1}", this.Latency, this.rtts.ToString()), this.Context);
|
||||
}
|
||||
|
||||
BufferPool.Release(pingFrame.OpaqueData);
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.GOAWAY:
|
||||
// Just exit from this thread. The processing thread will handle the frame too.
|
||||
|
||||
// Risking a double release here if the processing thread also consumed the goaway frame
|
||||
//if (Volatile.Read(ref this.threadExitCount) > 0)
|
||||
// BufferPool.Release(header.Payload);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch //(Exception ex)
|
||||
{
|
||||
//HTTPManager.Logger.Exception("HTTP2Handler", "", ex, this.Context);
|
||||
|
||||
//this.isRunning = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
TryToCleanup();
|
||||
HTTPManager.Logger.Information("HTTP2Handler", "Reader thread closing", this.Context);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryToCleanup()
|
||||
{
|
||||
this.isRunning = false;
|
||||
|
||||
// First thread closing notifies the ConnectionEventHelper
|
||||
int counter = Interlocked.Increment(ref this.threadExitCount);
|
||||
switch(counter)
|
||||
{
|
||||
case 1:
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, HTTPConnectionStates.Closed));
|
||||
break;
|
||||
|
||||
// Last thread closes the AutoResetEvent
|
||||
case 2:
|
||||
if (this.newFrameSignal != null)
|
||||
this.newFrameSignal.Close();
|
||||
this.newFrameSignal = null;
|
||||
|
||||
while (this.newFrames.TryDequeue(out var frame))
|
||||
BufferPool.Release(frame.Payload);
|
||||
break;
|
||||
default:
|
||||
HTTPManager.Logger.Warning("HTTP2Handler", String.Format("TryToCleanup - counter is {0}!", counter));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private double CalculateLatency()
|
||||
{
|
||||
if (this.rtts.Count == 0)
|
||||
return 0;
|
||||
|
||||
double sumLatency = 0;
|
||||
for (int i = 0; i < this.rtts.Count; ++i)
|
||||
sumLatency += this.rtts[i];
|
||||
|
||||
return sumLatency / this.rtts.Count;
|
||||
}
|
||||
|
||||
HTTP2Stream FindStreamById(UInt32 streamId)
|
||||
{
|
||||
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
|
||||
{
|
||||
var stream = this.clientInitiatedStreams[i];
|
||||
if (stream.Id == streamId)
|
||||
return stream;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ShutdownTypes ShutdownType { get; private set; }
|
||||
|
||||
public void Shutdown(ShutdownTypes type)
|
||||
{
|
||||
this.ShutdownType = type;
|
||||
|
||||
switch(this.ShutdownType)
|
||||
{
|
||||
case ShutdownTypes.Gentle:
|
||||
this.newFrameSignal.Set();
|
||||
break;
|
||||
|
||||
case ShutdownTypes.Immediate:
|
||||
this.conn.connector.Stream.Dispose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
HTTPRequest request = null;
|
||||
while (this.requestQueue.TryDequeue(out request))
|
||||
{
|
||||
HTTPManager.Logger.Information("HTTP2Handler", string.Format("Dispose - Request '{0}' IsCancellationRequested: {1}", request.CurrentUri.ToString(), request.IsCancellationRequested.ToString()), this.Context);
|
||||
if (request.IsCancellationRequested)
|
||||
{
|
||||
request.Response = null;
|
||||
request.State = HTTPRequestStates.Aborted;
|
||||
}
|
||||
else
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
using System;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
public sealed class WebSocketOverHTTP2Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Set it to false to disable Websocket Over HTTP/2 (RFC 8441). It's true by default.
|
||||
/// </summary>
|
||||
public bool EnableWebSocketOverHTTP2 { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Set it to disable fallback logic from the Websocket Over HTTP/2 implementation to the 'old' HTTP/1 implementation when it fails to connect.
|
||||
/// </summary>
|
||||
public bool EnableImplementationFallback { get; set; } = true;
|
||||
}
|
||||
|
||||
public sealed class HTTP2PluginSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum size of the HPACK header table.
|
||||
/// </summary>
|
||||
public UInt32 HeaderTableSize = 4096; // Spec default: 4096
|
||||
|
||||
/// <summary>
|
||||
/// Maximum concurrent http2 stream on http2 connection will allow. Its default value is 128;
|
||||
/// </summary>
|
||||
public UInt32 MaxConcurrentStreams = 128; // Spec default: not defined
|
||||
|
||||
/// <summary>
|
||||
/// Initial window size of a http2 stream. Its default value is 10 Mb (10 * 1024 * 1024).
|
||||
/// </summary>
|
||||
public UInt32 InitialStreamWindowSize = 10 * 1024 * 1024; // Spec default: 65535
|
||||
|
||||
/// <summary>
|
||||
/// Global window size of a http/2 connection. Its default value is the maximum possible value on 31 bits.
|
||||
/// </summary>
|
||||
public UInt32 InitialConnectionWindowSize = HTTP2Handler.MaxValueFor31Bits; // Spec default: 65535
|
||||
|
||||
/// <summary>
|
||||
/// Maximum size of a http2 frame.
|
||||
/// </summary>
|
||||
public UInt32 MaxFrameSize = 16384; // 16384 spec def.
|
||||
|
||||
/// <summary>
|
||||
/// Not used.
|
||||
/// </summary>
|
||||
public UInt32 MaxHeaderListSize = UInt32.MaxValue; // Spec default: infinite
|
||||
|
||||
/// <summary>
|
||||
/// With HTTP/2 only one connection will be open so we can keep it open longer as we hope it will be reused more.
|
||||
/// </summary>
|
||||
public TimeSpan MaxIdleTime = TimeSpan.FromSeconds(120);
|
||||
|
||||
/// <summary>
|
||||
/// Time between two ping messages.
|
||||
/// </summary>
|
||||
public TimeSpan PingFrequency = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// Timeout to receive a ping acknowledgement from the server. If no ack reveived in this time the connection will be treated as broken.
|
||||
/// </summary>
|
||||
public TimeSpan Timeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to enable RFC 8441 "Bootstrapping WebSockets with HTTP/2" (https://tools.ietf.org/html/rfc8441).
|
||||
/// </summary>
|
||||
public bool EnableConnectProtocol = false;
|
||||
|
||||
/// <summary>
|
||||
/// Settings for WebSockets over HTTP/2 (RFC 8441)
|
||||
/// </summary>
|
||||
public WebSocketOverHTTP2Settings WebSocketOverHTTP2Settings = new WebSocketOverHTTP2Settings();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
|
||||
using BestHTTP.Core;
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
public sealed class HTTP2Response : HTTPResponse
|
||||
{
|
||||
// For progress report
|
||||
public long ExpectedContentLength { get; private set; }
|
||||
public bool HasContentEncoding { get => !string.IsNullOrEmpty(this.contentEncoding); }
|
||||
|
||||
private string contentEncoding = null;
|
||||
|
||||
bool isPrepared;
|
||||
private Decompression.IDecompressor decompressor;
|
||||
|
||||
public HTTP2Response(HTTPRequest request, bool isFromCache)
|
||||
: base(request, isFromCache)
|
||||
{
|
||||
this.VersionMajor = 2;
|
||||
this.VersionMinor = 0;
|
||||
}
|
||||
|
||||
internal void AddHeaders(List<KeyValuePair<string, string>> headers)
|
||||
{
|
||||
this.ExpectedContentLength = -1;
|
||||
Dictionary<string, List<string>> newHeaders = this.baseRequest.OnHeadersReceived != null ? new Dictionary<string, List<string>>() : null;
|
||||
|
||||
for (int i = 0; i < headers.Count; ++i)
|
||||
{
|
||||
KeyValuePair<string, string> header = headers[i];
|
||||
|
||||
if (header.Key.Equals(":status", StringComparison.Ordinal))
|
||||
{
|
||||
base.StatusCode = int.Parse(header.Value);
|
||||
base.Message = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this.HasContentEncoding && header.Key.Equals("content-encoding", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
this.contentEncoding = header.Value;
|
||||
}
|
||||
else if (base.baseRequest.OnDownloadProgress != null && header.Key.Equals("content-length", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
long contentLength;
|
||||
if (long.TryParse(header.Value, out contentLength))
|
||||
this.ExpectedContentLength = contentLength;
|
||||
else
|
||||
HTTPManager.Logger.Information("HTTP2Response", string.Format("AddHeaders - Can't parse Content-Length as an int: '{0}'", header.Value), this.baseRequest.Context, this.Context);
|
||||
}
|
||||
|
||||
base.AddHeader(header.Key, header.Value);
|
||||
}
|
||||
|
||||
if (newHeaders != null)
|
||||
{
|
||||
List<string> values;
|
||||
if (!newHeaders.TryGetValue(header.Key, out values))
|
||||
newHeaders.Add(header.Key, values = new List<string>(1));
|
||||
|
||||
values.Add(header.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.ExpectedContentLength == -1 && base.baseRequest.OnDownloadProgress != null)
|
||||
HTTPManager.Logger.Information("HTTP2Response", "AddHeaders - No Content-Length header found!", this.baseRequest.Context, this.Context);
|
||||
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, newHeaders));
|
||||
}
|
||||
|
||||
internal void AddData(Stream stream)
|
||||
{
|
||||
if (this.HasContentEncoding)
|
||||
{
|
||||
Stream decoderStream = Decompression.DecompressorFactory.GetDecoderStream(stream, this.contentEncoding);
|
||||
|
||||
if (decoderStream == null)
|
||||
{
|
||||
base.Data = new byte[stream.Length];
|
||||
stream.Read(base.Data, 0, (int)stream.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var ms = new BufferPoolMemoryStream((int)stream.Length))
|
||||
{
|
||||
var buf = BufferPool.Get(HTTPResponse.MinReadBufferSize, true);
|
||||
int byteCount = 0;
|
||||
|
||||
while ((byteCount = decoderStream.Read(buf, 0, buf.Length)) > 0)
|
||||
ms.Write(buf, 0, byteCount);
|
||||
|
||||
BufferPool.Release(buf);
|
||||
|
||||
base.Data = ms.ToArray();
|
||||
}
|
||||
|
||||
decoderStream.Dispose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Data = new byte[stream.Length];
|
||||
stream.Read(base.Data, 0, (int)stream.Length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal void ProcessData(byte[] payload, int payloadLength)
|
||||
{
|
||||
if (!this.isPrepared)
|
||||
{
|
||||
this.isPrepared = true;
|
||||
base.BeginReceiveStreamFragments();
|
||||
}
|
||||
|
||||
if (this.HasContentEncoding)
|
||||
{
|
||||
if (this.decompressor == null)
|
||||
this.decompressor = Decompression.DecompressorFactory.GetDecompressor(this.contentEncoding, this.Context);
|
||||
var result = this.decompressor.Decompress(payload, 0, payloadLength, true, true);
|
||||
|
||||
base.FeedStreamFragment(result.Data, 0, result.Length);
|
||||
}
|
||||
else
|
||||
base.FeedStreamFragment(payload, 0, payloadLength);
|
||||
}
|
||||
|
||||
internal void FinishProcessData()
|
||||
{
|
||||
base.FlushRemainingFragmentBuffer();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
if (this.decompressor != null)
|
||||
{
|
||||
this.decompressor.Dispose();
|
||||
this.decompressor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#iana-settings
|
||||
public enum HTTP2Settings : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows the sender to inform the remote endpoint of the maximum size of the
|
||||
/// header compression table used to decode header blocks, in octets.
|
||||
/// The encoder can select any size equal to or less than this value
|
||||
/// by using signaling specific to the header compression format inside a header block (see [COMPRESSION]).
|
||||
/// The initial value is 4,096 octets.
|
||||
/// </summary>
|
||||
HEADER_TABLE_SIZE = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// This setting can be used to disable server push (Section 8.2).
|
||||
/// An endpoint MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a value of 0.
|
||||
/// An endpoint that has both set this parameter to 0 and had it acknowledged MUST treat the receipt of a
|
||||
/// PUSH_PROMISE frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
||||
///
|
||||
/// The initial value is 1, which indicates that server push is permitted.
|
||||
/// Any value other than 0 or 1 MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
||||
/// </summary>
|
||||
ENABLE_PUSH = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the maximum number of concurrent streams that the sender will allow. This limit is directional:
|
||||
/// it applies to the number of streams that the sender permits the receiver to create.
|
||||
/// Initially, there is no limit to this value. It is recommended that this value be no smaller than 100,
|
||||
/// so as to not unnecessarily limit parallelism.
|
||||
///
|
||||
/// A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as special by endpoints.
|
||||
/// A zero value does prevent the creation of new streams;
|
||||
/// however, this can also happen for any limit that is exhausted with active streams.
|
||||
/// Servers SHOULD only set a zero value for short durations; if a server does not wish to accept requests,
|
||||
/// closing the connection is more appropriate.
|
||||
/// </summary>
|
||||
MAX_CONCURRENT_STREAMS = 0x03,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the sender's initial window size (in octets) for stream-level flow control.
|
||||
/// The initial value is 2^16-1 (65,535) octets.
|
||||
///
|
||||
/// This setting affects the window size of all streams (see Section 6.9.2).
|
||||
///
|
||||
/// Values above the maximum flow-control window size of 2^31-1 MUST be treated as a connection error
|
||||
/// (Section 5.4.1) of type FLOW_CONTROL_ERROR.
|
||||
/// </summary>
|
||||
INITIAL_WINDOW_SIZE = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the size of the largest frame payload that the sender is willing to receive, in octets.
|
||||
///
|
||||
/// The initial value is 2^14 (16,384) octets.
|
||||
/// The value advertised by an endpoint MUST be between this initial value and the maximum allowed frame size
|
||||
/// (2^24-1 or 16,777,215 octets), inclusive.
|
||||
/// Values outside this range MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
||||
/// </summary>
|
||||
MAX_FRAME_SIZE = 0x05,
|
||||
|
||||
/// <summary>
|
||||
/// This advisory setting informs a peer of the maximum size of header list that the sender is prepared to accept, in octets.
|
||||
/// The value is based on the uncompressed size of header fields,
|
||||
/// including the length of the name and value in octets plus an overhead of 32 octets for each header field.
|
||||
///
|
||||
/// For any given request, a lower limit than what is advertised MAY be enforced. The initial value of this setting is unlimited.
|
||||
/// </summary>
|
||||
MAX_HEADER_LIST_SIZE = 0x06,
|
||||
|
||||
RESERVED = 0x07,
|
||||
|
||||
/// <summary>
|
||||
/// https://tools.ietf.org/html/rfc8441
|
||||
/// Upon receipt of SETTINGS_ENABLE_CONNECT_PROTOCOL with a value of 1, a client MAY use the Extended CONNECT as defined in this document when creating new streams.
|
||||
/// Receipt of this parameter by a server does not have any impact.
|
||||
///
|
||||
/// A sender MUST NOT send a SETTINGS_ENABLE_CONNECT_PROTOCOL parameter with the value of 0 after previously sending a value of 1.
|
||||
/// </summary>
|
||||
ENABLE_CONNECT_PROTOCOL = 0x08
|
||||
}
|
||||
|
||||
public sealed class HTTP2SettingsRegistry
|
||||
{
|
||||
public bool IsReadOnly { get; private set; }
|
||||
public Action<HTTP2SettingsRegistry, HTTP2Settings, UInt32, UInt32> OnSettingChangedEvent;
|
||||
|
||||
private UInt32[] values;
|
||||
private bool[] changeFlags;
|
||||
|
||||
public UInt32 this[HTTP2Settings setting]
|
||||
{
|
||||
get { return this.values[(ushort)setting]; }
|
||||
|
||||
set
|
||||
{
|
||||
if (this.IsReadOnly)
|
||||
throw new NotSupportedException("It's a read-only one!");
|
||||
|
||||
ushort idx = (ushort)setting;
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#SettingValues
|
||||
// An endpoint that receives a SETTINGS frame with any unknown or unsupported identifier MUST ignore that setting.
|
||||
if (idx == 0 || idx >= this.values.Length)
|
||||
return;
|
||||
|
||||
UInt32 oldValue = this.values[idx];
|
||||
if (oldValue != value)
|
||||
{
|
||||
this.values[idx] = value;
|
||||
this.changeFlags[idx] = true;
|
||||
IsChanged = true;
|
||||
|
||||
if (this.OnSettingChangedEvent != null)
|
||||
this.OnSettingChangedEvent(this, setting, oldValue, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsChanged { get; private set; }
|
||||
|
||||
private HTTP2SettingsManager _parent;
|
||||
|
||||
public HTTP2SettingsRegistry(HTTP2SettingsManager parent, bool readOnly, bool treatItAsAlreadyChanged)
|
||||
{
|
||||
this._parent = parent;
|
||||
|
||||
this.values = new UInt32[HTTP2SettingsManager.SettingsCount];
|
||||
|
||||
this.IsReadOnly = readOnly;
|
||||
if (!this.IsReadOnly)
|
||||
this.changeFlags = new bool[HTTP2SettingsManager.SettingsCount];
|
||||
|
||||
// Set default values (https://httpwg.org/specs/rfc7540.html#iana-settings)
|
||||
this.values[(UInt16)HTTP2Settings.HEADER_TABLE_SIZE] = 4096;
|
||||
this.values[(UInt16)HTTP2Settings.ENABLE_PUSH] = 1;
|
||||
this.values[(UInt16)HTTP2Settings.MAX_CONCURRENT_STREAMS] = 128;
|
||||
this.values[(UInt16)HTTP2Settings.INITIAL_WINDOW_SIZE] = 65535;
|
||||
this.values[(UInt16)HTTP2Settings.MAX_FRAME_SIZE] = 16384;
|
||||
this.values[(UInt16)HTTP2Settings.MAX_HEADER_LIST_SIZE] = UInt32.MaxValue; // infinite
|
||||
|
||||
if (this.IsChanged = treatItAsAlreadyChanged)
|
||||
{
|
||||
this.changeFlags[(UInt16)HTTP2Settings.MAX_CONCURRENT_STREAMS] = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Merge(List<KeyValuePair<HTTP2Settings, UInt32>> settings)
|
||||
{
|
||||
if (settings == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < settings.Count; ++i)
|
||||
{
|
||||
HTTP2Settings setting = settings[i].Key;
|
||||
UInt16 key = (UInt16)setting;
|
||||
UInt32 value = settings[i].Value;
|
||||
|
||||
if (key > 0 && key <= HTTP2SettingsManager.SettingsCount)
|
||||
{
|
||||
UInt32 oldValue = this.values[key];
|
||||
this.values[key] = value;
|
||||
|
||||
if (oldValue != value && this.OnSettingChangedEvent != null)
|
||||
this.OnSettingChangedEvent(this, setting, oldValue, value);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Information("HTTP2SettingsRegistry", string.Format("Merge {0}({1}) = {2}", setting, key, value), this._parent.Parent.Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Merge(HTTP2SettingsRegistry from)
|
||||
{
|
||||
if (this.values != null)
|
||||
this.values = new uint[from.values.Length];
|
||||
|
||||
for (int i = 0; i < this.values.Length; ++i)
|
||||
this.values[i] = from.values[i];
|
||||
}
|
||||
|
||||
internal HTTP2FrameHeaderAndPayload CreateFrame()
|
||||
{
|
||||
List<KeyValuePair<HTTP2Settings, UInt32>> keyValuePairs = new List<KeyValuePair<HTTP2Settings, uint>>(HTTP2SettingsManager.SettingsCount);
|
||||
|
||||
for (int i = 1; i < HTTP2SettingsManager.SettingsCount; ++i)
|
||||
if (this.changeFlags[i])
|
||||
{
|
||||
keyValuePairs.Add(new KeyValuePair<HTTP2Settings, uint>((HTTP2Settings)i, this[(HTTP2Settings)i]));
|
||||
this.changeFlags[i] = false;
|
||||
}
|
||||
|
||||
this.IsChanged = false;
|
||||
|
||||
return HTTP2FrameHelper.CreateSettingsFrame(keyValuePairs);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class HTTP2SettingsManager
|
||||
{
|
||||
public static readonly int SettingsCount = Enum.GetNames(typeof(HTTP2Settings)).Length + 1;
|
||||
|
||||
/// <summary>
|
||||
/// This is the ACKd or default settings that we sent to the server.
|
||||
/// </summary>
|
||||
public HTTP2SettingsRegistry MySettings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is the setting that can be changed. It will be sent to the server ASAP, and when ACKd, it will be copied
|
||||
/// to MySettings.
|
||||
/// </summary>
|
||||
public HTTP2SettingsRegistry InitiatedMySettings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Settings of the remote peer
|
||||
/// </summary>
|
||||
public HTTP2SettingsRegistry RemoteSettings { get; private set; }
|
||||
|
||||
public DateTime SettingsChangesSentAt { get; private set; }
|
||||
|
||||
public HTTP2Handler Parent { get; private set; }
|
||||
|
||||
public HTTP2SettingsManager(HTTP2Handler parentHandler)
|
||||
{
|
||||
this.Parent = parentHandler;
|
||||
|
||||
this.MySettings = new HTTP2SettingsRegistry(this, readOnly: true, treatItAsAlreadyChanged: false);
|
||||
this.InitiatedMySettings = new HTTP2SettingsRegistry(this, readOnly: false, treatItAsAlreadyChanged: true);
|
||||
this.RemoteSettings = new HTTP2SettingsRegistry(this, readOnly: true, treatItAsAlreadyChanged: false);
|
||||
this.SettingsChangesSentAt = DateTime.MinValue;
|
||||
}
|
||||
|
||||
internal void Process(HTTP2FrameHeaderAndPayload frame, List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
if (frame.Type != HTTP2FrameTypes.SETTINGS)
|
||||
return;
|
||||
|
||||
HTTP2SettingsFrame settingsFrame = HTTP2FrameHelper.ReadSettings(frame);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
|
||||
HTTPManager.Logger.Information("HTTP2SettingsManager", "Processing Settings frame: " + settingsFrame.ToString(), this.Parent.Context);
|
||||
|
||||
if ((settingsFrame.Flags & HTTP2SettingsFlags.ACK) == HTTP2SettingsFlags.ACK)
|
||||
{
|
||||
this.MySettings.Merge(this.InitiatedMySettings);
|
||||
this.SettingsChangesSentAt = DateTime.MinValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.RemoteSettings.Merge(settingsFrame.Settings);
|
||||
outgoingFrames.Add(HTTP2FrameHelper.CreateACKSettingsFrame());
|
||||
}
|
||||
}
|
||||
|
||||
internal void SendChanges(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
if (this.SettingsChangesSentAt != DateTime.MinValue && DateTime.UtcNow - this.SettingsChangesSentAt >= TimeSpan.FromSeconds(10))
|
||||
{
|
||||
HTTPManager.Logger.Error("HTTP2SettingsManager", "No ACK received for settings frame!", this.Parent.Context);
|
||||
this.SettingsChangesSentAt = DateTime.MinValue;
|
||||
}
|
||||
|
||||
// Upon receiving a SETTINGS frame with the ACK flag set, the sender of the altered parameters can rely on the setting having been applied.
|
||||
if (!this.InitiatedMySettings.IsChanged)
|
||||
return;
|
||||
|
||||
outgoingFrames.Add(this.InitiatedMySettings.CreateFrame());
|
||||
this.SettingsChangesSentAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,657 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BestHTTP.Core;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using BestHTTP.Logger;
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
using BestHTTP.Caching;
|
||||
#endif
|
||||
|
||||
using BestHTTP.Timings;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
// https://httpwg.org/specs/rfc7540.html#StreamStates
|
||||
//
|
||||
// Idle
|
||||
// |
|
||||
// V
|
||||
// Open
|
||||
// Receive END_STREAM / | \ Send END_STREAM
|
||||
// v |R V
|
||||
// Half Closed Remote |S Half Closed Locale
|
||||
// \ |T /
|
||||
// Send END_STREAM | RST_STREAM \ | / Receive END_STREAM | RST_STREAM
|
||||
// Receive RST_STREAM \ | / Send RST_STREAM
|
||||
// V
|
||||
// Closed
|
||||
//
|
||||
// IDLE -> send headers -> OPEN -> send data -> HALF CLOSED - LOCAL -> receive headers -> receive Data -> CLOSED
|
||||
// | ^ | ^
|
||||
// +-------------------------------------+ +-----------------------------+
|
||||
// END_STREAM flag present? END_STREAM flag present?
|
||||
//
|
||||
|
||||
public enum HTTP2StreamStates
|
||||
{
|
||||
Idle,
|
||||
//ReservedLocale,
|
||||
//ReservedRemote,
|
||||
Open,
|
||||
HalfClosedLocal,
|
||||
HalfClosedRemote,
|
||||
Closed
|
||||
}
|
||||
|
||||
public class HTTP2Stream
|
||||
{
|
||||
public UInt32 Id { get; private set; }
|
||||
|
||||
public HTTP2StreamStates State {
|
||||
get { return this._state; }
|
||||
|
||||
protected set {
|
||||
var oldState = this._state;
|
||||
|
||||
this._state = value;
|
||||
|
||||
if (oldState != this._state)
|
||||
{
|
||||
//this.lastStateChangedAt = DateTime.Now;
|
||||
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] State changed from {1} to {2}", this.Id, oldState, this._state), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
private HTTP2StreamStates _state;
|
||||
|
||||
//protected DateTime lastStateChangedAt;
|
||||
//protected TimeSpan TimeSpentInCurrentState { get { return DateTime.Now - this.lastStateChangedAt; } }
|
||||
|
||||
/// <summary>
|
||||
/// This flag is checked by the connection to decide whether to do a new processing-frame sending round before sleeping until new data arrives
|
||||
/// </summary>
|
||||
public virtual bool HasFrameToSend
|
||||
{
|
||||
get
|
||||
{
|
||||
// Don't let the connection sleep until
|
||||
return this.outgoing.Count > 0 || // we already booked at least one frame in advance
|
||||
(this.State == HTTP2StreamStates.Open && this.remoteWindow > 0 && this.lastReadCount > 0); // we are in the middle of sending request data
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Next interaction scheduled by the stream relative to *now*. Its default is TimeSpan.MaxValue == no interaction.
|
||||
/// </summary>
|
||||
public virtual TimeSpan NextInteraction { get; } = TimeSpan.MaxValue;
|
||||
|
||||
public HTTPRequest AssignedRequest { get; protected set; }
|
||||
|
||||
public LoggingContext Context { get; protected set; }
|
||||
|
||||
protected bool isStreamedDownload;
|
||||
protected uint downloaded;
|
||||
|
||||
protected HTTPRequest.UploadStreamInfo uploadStreamInfo;
|
||||
|
||||
protected HTTP2SettingsManager settings;
|
||||
protected HPACKEncoder encoder;
|
||||
|
||||
// Outgoing frames. The stream will send one frame per Process call, but because one step might be able to
|
||||
// generate more than one frames, we use a list.
|
||||
protected Queue<HTTP2FrameHeaderAndPayload> outgoing = new Queue<HTTP2FrameHeaderAndPayload>();
|
||||
|
||||
protected Queue<HTTP2FrameHeaderAndPayload> incomingFrames = new Queue<HTTP2FrameHeaderAndPayload>();
|
||||
|
||||
protected FramesAsStreamView headerView;
|
||||
protected FramesAsStreamView dataView;
|
||||
|
||||
protected UInt32 localWindow;
|
||||
protected Int64 remoteWindow;
|
||||
|
||||
protected uint windowUpdateThreshold;
|
||||
|
||||
protected UInt32 sentData;
|
||||
|
||||
protected bool isRSTFrameSent;
|
||||
protected bool isEndSTRReceived;
|
||||
|
||||
protected HTTP2Response response;
|
||||
|
||||
protected HTTP2Handler parent;
|
||||
protected int lastReadCount;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to create a client stream.
|
||||
/// </summary>
|
||||
public HTTP2Stream(UInt32 id, HTTP2Handler parentHandler, HTTP2SettingsManager registry, HPACKEncoder hpackEncoder)
|
||||
{
|
||||
this.Id = id;
|
||||
this.parent = parentHandler;
|
||||
this.settings = registry;
|
||||
this.encoder = hpackEncoder;
|
||||
|
||||
this.remoteWindow = this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
|
||||
this.settings.RemoteSettings.OnSettingChangedEvent += OnRemoteSettingChanged;
|
||||
|
||||
// Room for improvement: If INITIAL_WINDOW_SIZE is small (what we can consider a 'small' value?), threshold must be higher
|
||||
this.windowUpdateThreshold = (uint)(this.remoteWindow / 2);
|
||||
|
||||
this.Context = new LoggingContext(this);
|
||||
this.Context.Add("id", id);
|
||||
}
|
||||
|
||||
public virtual void Assign(HTTPRequest request)
|
||||
{
|
||||
if (request.IsRedirected)
|
||||
request.Timing.Add(TimingEventNames.Queued_For_Redirection);
|
||||
else
|
||||
request.Timing.Add(TimingEventNames.Queued);
|
||||
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Request assigned to stream. Remote Window: {1:N0}. Uri: {2}", this.Id, this.remoteWindow, request.CurrentUri.ToString()), this.Context, request.Context, this.parent.Context);
|
||||
this.AssignedRequest = request;
|
||||
this.isStreamedDownload = request.UseStreaming && request.OnStreamingData != null;
|
||||
this.downloaded = 0;
|
||||
}
|
||||
|
||||
public void Process(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
if (this.AssignedRequest.IsCancellationRequested && !this.isRSTFrameSent)
|
||||
{
|
||||
// These two are already set in HTTPRequest's Abort().
|
||||
//this.AssignedRequest.Response = null;
|
||||
//this.AssignedRequest.State = this.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
|
||||
|
||||
this.outgoing.Clear();
|
||||
if (this.State != HTTP2StreamStates.Idle)
|
||||
this.outgoing.Enqueue(HTTP2FrameHelper.CreateRSTFrame(this.Id, HTTP2ErrorCodes.CANCEL));
|
||||
|
||||
// We can close the stream if already received headers, or not even sent one
|
||||
if (this.State == HTTP2StreamStates.HalfClosedRemote || this.State == HTTP2StreamStates.Idle)
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
|
||||
this.isRSTFrameSent = true;
|
||||
}
|
||||
|
||||
// 1.) Go through incoming frames
|
||||
ProcessIncomingFrames(outgoingFrames);
|
||||
|
||||
// 2.) Create outgoing frames based on the stream's state and the request processing state.
|
||||
ProcessState(outgoingFrames);
|
||||
|
||||
// 3.) Send one frame per Process call
|
||||
if (this.outgoing.Count > 0)
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload frame = this.outgoing.Dequeue();
|
||||
|
||||
outgoingFrames.Add(frame);
|
||||
|
||||
// If END_Stream in header or data frame is present => half closed local
|
||||
if ((frame.Type == HTTP2FrameTypes.HEADERS && (frame.Flags & (byte)HTTP2HeadersFlags.END_STREAM) != 0) ||
|
||||
(frame.Type == HTTP2FrameTypes.DATA && (frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0))
|
||||
{
|
||||
this.State = HTTP2StreamStates.HalfClosedLocal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddFrame(HTTP2FrameHeaderAndPayload frame, List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
// Room for improvement: error check for forbidden frames (like settings) and stream state
|
||||
|
||||
this.incomingFrames.Enqueue(frame);
|
||||
|
||||
ProcessIncomingFrames(outgoingFrames);
|
||||
}
|
||||
|
||||
public void Abort(string msg)
|
||||
{
|
||||
if (this.AssignedRequest.State != HTTPRequestStates.Processing)
|
||||
{
|
||||
// do nothing, its state is already set.
|
||||
}
|
||||
else if (this.AssignedRequest.IsCancellationRequested)
|
||||
{
|
||||
// These two are already set in HTTPRequest's Abort().
|
||||
//this.AssignedRequest.Response = null;
|
||||
//this.AssignedRequest.State = this.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
|
||||
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
}
|
||||
else if (this.AssignedRequest.Retries >= this.AssignedRequest.MaxRetries)
|
||||
{
|
||||
this.AssignedRequest.Response = null;
|
||||
this.AssignedRequest.Exception = new Exception(msg);
|
||||
this.AssignedRequest.State = HTTPRequestStates.Error;
|
||||
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.AssignedRequest.Retries++;
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.Resend));
|
||||
}
|
||||
|
||||
this.Removed();
|
||||
}
|
||||
|
||||
protected void ProcessIncomingFrames(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
UInt32 windowUpdate = 0;
|
||||
|
||||
while (this.incomingFrames.Count > 0)
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload frame = this.incomingFrames.Dequeue();
|
||||
|
||||
if ((this.isRSTFrameSent || this.AssignedRequest.IsCancellationRequested) && frame.Type != HTTP2FrameTypes.HEADERS && frame.Type != HTTP2FrameTypes.CONTINUATION)
|
||||
{
|
||||
BufferPool.Release(frame.Payload);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (/*HTTPManager.Logger.Level == Logger.Loglevels.All && */frame.Type != HTTP2FrameTypes.DATA && frame.Type != HTTP2FrameTypes.WINDOW_UPDATE)
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Process - processing frame: {1}", this.Id, frame.ToString()), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
|
||||
switch (frame.Type)
|
||||
{
|
||||
case HTTP2FrameTypes.HEADERS:
|
||||
case HTTP2FrameTypes.CONTINUATION:
|
||||
if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open && this.State != HTTP2StreamStates.Idle)
|
||||
{
|
||||
// ERROR!
|
||||
continue;
|
||||
}
|
||||
|
||||
// payload will be released by the view
|
||||
frame.DontUseMemPool = true;
|
||||
|
||||
if (this.headerView == null)
|
||||
{
|
||||
this.AssignedRequest.Timing.Add(TimingEventNames.Waiting_TTFB);
|
||||
this.headerView = new FramesAsStreamView(new HeaderFrameView());
|
||||
}
|
||||
|
||||
this.headerView.AddFrame(frame);
|
||||
|
||||
// END_STREAM may arrive sooner than an END_HEADERS, so we have to store that we already received it
|
||||
if ((frame.Flags & (byte)HTTP2HeadersFlags.END_STREAM) != 0)
|
||||
this.isEndSTRReceived = true;
|
||||
|
||||
if ((frame.Flags & (byte)HTTP2HeadersFlags.END_HEADERS) != 0)
|
||||
{
|
||||
List<KeyValuePair<string, string>> headers = new List<KeyValuePair<string, string>>();
|
||||
|
||||
try
|
||||
{
|
||||
this.encoder.Decode(this, this.headerView, headers);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("HTTP2Stream", string.Format("[{0}] ProcessIncomingFrames - Header Frames: {1}, Encoder: {2}", this.Id, this.headerView.ToString(), this.encoder.ToString()), ex, this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
}
|
||||
|
||||
this.headerView.Close();
|
||||
this.headerView = null;
|
||||
|
||||
this.AssignedRequest.Timing.Add(TimingEventNames.Headers);
|
||||
|
||||
if (this.isRSTFrameSent)
|
||||
{
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.response == null)
|
||||
this.AssignedRequest.Response = this.response = new HTTP2Response(this.AssignedRequest, false);
|
||||
|
||||
this.response.AddHeaders(headers);
|
||||
|
||||
if (this.isEndSTRReceived)
|
||||
{
|
||||
// If there's any trailing header, no data frame has an END_STREAM flag
|
||||
if (this.isStreamedDownload)
|
||||
this.response.FinishProcessData();
|
||||
|
||||
PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTP2Stream, FramesAsStreamView>(FinishRequest, this, this.dataView);
|
||||
|
||||
this.dataView = null;
|
||||
|
||||
if (this.State == HTTP2StreamStates.HalfClosedLocal)
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
else
|
||||
this.State = HTTP2StreamStates.HalfClosedRemote;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.DATA:
|
||||
ProcessIncomingDATAFrame(ref frame, ref windowUpdate);
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.WINDOW_UPDATE:
|
||||
HTTP2WindowUpdateFrame windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(frame);
|
||||
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Received Window Update: {1:N0}, new remoteWindow: {2:N0}, initial remote window: {3:N0}, total data sent: {4:N0}", this.Id, windowUpdateFrame.WindowSizeIncrement, this.remoteWindow + windowUpdateFrame.WindowSizeIncrement, this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE], this.sentData), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
|
||||
this.remoteWindow += windowUpdateFrame.WindowSizeIncrement;
|
||||
break;
|
||||
|
||||
case HTTP2FrameTypes.RST_STREAM:
|
||||
// https://httpwg.org/specs/rfc7540.html#RST_STREAM
|
||||
|
||||
// It's possible to receive an RST_STREAM on a closed stream. In this case, we have to ignore it.
|
||||
if (this.State == HTTP2StreamStates.Closed)
|
||||
break;
|
||||
|
||||
var rstStreamFrame = HTTP2FrameHelper.ReadRST_StreamFrame(frame);
|
||||
|
||||
//HTTPManager.Logger.Error("HTTP2Stream", string.Format("[{0}] RST Stream frame ({1}) received in state {2}!", this.Id, rstStreamFrame, this.State), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
|
||||
Abort(string.Format("RST_STREAM frame received! Error code: {0}({1})", rstStreamFrame.Error.ToString(), rstStreamFrame.ErrorCode));
|
||||
break;
|
||||
|
||||
default:
|
||||
HTTPManager.Logger.Warning("HTTP2Stream", string.Format("[{0}] Unexpected frame ({1}, Payload: {2}) in state {3}!", this.Id, frame, frame.PayloadAsHex(), this.State), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!frame.DontUseMemPool)
|
||||
BufferPool.Release(frame.Payload);
|
||||
}
|
||||
|
||||
// 2023.07.08: Even if State is Closed, we should send back a window update.
|
||||
// Not because of this stream, but for the global window update.
|
||||
if (windowUpdate > 0 /*&& this.State != HTTP2StreamStates.Closed*/)
|
||||
{
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Sending window update: {1:N0}, current window: {2:N0}, initial window size: {3:N0}", this.Id, windowUpdate, this.localWindow, this.settings.MySettings[HTTP2Settings.INITIAL_WINDOW_SIZE]), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
|
||||
this.localWindow += windowUpdate;
|
||||
|
||||
outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, windowUpdate));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ProcessIncomingDATAFrame(ref HTTP2FrameHeaderAndPayload frame, ref uint windowUpdate)
|
||||
{
|
||||
if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open)
|
||||
{
|
||||
// ERROR!
|
||||
return;
|
||||
}
|
||||
|
||||
this.downloaded += frame.PayloadLength;
|
||||
|
||||
if (this.isStreamedDownload && frame.Payload != null && frame.PayloadLength > 0)
|
||||
this.response.ProcessData(frame.Payload, (int)frame.PayloadLength);
|
||||
|
||||
// frame's buffer will be released by the frames view
|
||||
frame.DontUseMemPool = !this.isStreamedDownload;
|
||||
|
||||
if (this.dataView == null && !this.isStreamedDownload)
|
||||
this.dataView = new FramesAsStreamView(new DataFrameView());
|
||||
|
||||
if (!this.isStreamedDownload)
|
||||
this.dataView.AddFrame(frame);
|
||||
|
||||
// Track received data, and if necessary(local window getting too low), send a window update frame
|
||||
if (this.localWindow < frame.PayloadLength)
|
||||
{
|
||||
HTTPManager.Logger.Error("HTTP2Stream", string.Format("[{0}] Frame's PayloadLength ({1:N0}) is larger then local window ({2:N0}). Frame: {3}", this.Id, frame.PayloadLength, this.localWindow, frame), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
}
|
||||
else
|
||||
this.localWindow -= frame.PayloadLength;
|
||||
|
||||
if ((frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0)
|
||||
this.isEndSTRReceived = true;
|
||||
|
||||
// Window update logic.
|
||||
// 1.) We could use a logic to only send window update(s) after a threshold is reached.
|
||||
// When the initial window size is high enough to contain the whole or most of the result,
|
||||
// sending back two window updates (connection and stream) after every data frame is pointless.
|
||||
// 2.) On the other hand, window updates are cheap and works even when initial window size is low.
|
||||
// (
|
||||
if (this.isEndSTRReceived || this.localWindow <= this.windowUpdateThreshold)
|
||||
windowUpdate += this.settings.MySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] - this.localWindow - windowUpdate;
|
||||
|
||||
if (this.isEndSTRReceived)
|
||||
{
|
||||
if (this.isStreamedDownload)
|
||||
this.response.FinishProcessData();
|
||||
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] All data arrived, data length: {1:N0}", this.Id, this.downloaded), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
|
||||
// create a short living thread to process the downloaded data:
|
||||
PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTP2Stream, FramesAsStreamView>(FinishRequest, this, this.dataView);
|
||||
|
||||
this.dataView = null;
|
||||
|
||||
if (this.State == HTTP2StreamStates.HalfClosedLocal)
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
else
|
||||
this.State = HTTP2StreamStates.HalfClosedRemote;
|
||||
}
|
||||
else if (this.AssignedRequest.OnDownloadProgress != null)
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest,
|
||||
RequestEvents.DownloadProgress,
|
||||
downloaded,
|
||||
this.response.ExpectedContentLength));
|
||||
}
|
||||
|
||||
protected void ProcessState(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
switch (this.State)
|
||||
{
|
||||
case HTTP2StreamStates.Idle:
|
||||
|
||||
UInt32 initiatedInitialWindowSize = this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
|
||||
this.localWindow = initiatedInitialWindowSize;
|
||||
// window update with a zero increment would be an error (https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE)
|
||||
//if (HTTP2Connection.MaxValueFor31Bits > initiatedInitialWindowSize)
|
||||
// this.outgoing.Enqueue(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, HTTP2Connection.MaxValueFor31Bits - initiatedInitialWindowSize));
|
||||
//this.localWindow = HTTP2Connection.MaxValueFor31Bits;
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
// Setup cache control headers before we send out the request
|
||||
if (!this.AssignedRequest.DisableCache)
|
||||
HTTPCacheService.SetHeaders(this.AssignedRequest);
|
||||
#endif
|
||||
|
||||
// hpack encode the request's headers
|
||||
this.encoder.Encode(this, this.AssignedRequest, this.outgoing, this.Id);
|
||||
|
||||
// HTTP/2 uses DATA frames to carry message payloads.
|
||||
// The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in HTTP/2.
|
||||
this.uploadStreamInfo = this.AssignedRequest.GetUpStream();
|
||||
|
||||
//this.State = HTTP2StreamStates.Open;
|
||||
|
||||
if (this.uploadStreamInfo.Stream == null)
|
||||
{
|
||||
this.State = HTTP2StreamStates.HalfClosedLocal;
|
||||
this.AssignedRequest.Timing.Add(TimingEventNames.Request_Sent);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.State = HTTP2StreamStates.Open;
|
||||
this.lastReadCount = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTP2StreamStates.Open:
|
||||
ProcessOpenState(outgoingFrames);
|
||||
//HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] New DATA frame created! remoteWindow: {1:N0}", this.Id, this.remoteWindow), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
break;
|
||||
|
||||
case HTTP2StreamStates.HalfClosedLocal:
|
||||
break;
|
||||
|
||||
case HTTP2StreamStates.HalfClosedRemote:
|
||||
break;
|
||||
|
||||
case HTTP2StreamStates.Closed:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ProcessOpenState(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
// remote Window can be negative! See https://httpwg.org/specs/rfc7540.html#InitialWindowSize
|
||||
if (this.remoteWindow <= 0)
|
||||
{
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Skipping data sending as remote Window is {1}!", this.Id, this.remoteWindow), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
return;
|
||||
}
|
||||
|
||||
// This step will send one frame per OpenState call.
|
||||
|
||||
Int64 maxFrameSize = Math.Min(HTTPRequest.UploadChunkSize, Math.Min(this.remoteWindow, this.settings.RemoteSettings[HTTP2Settings.MAX_FRAME_SIZE]));
|
||||
|
||||
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
|
||||
frame.Type = HTTP2FrameTypes.DATA;
|
||||
frame.StreamId = this.Id;
|
||||
|
||||
frame.Payload = BufferPool.Get(maxFrameSize, true);
|
||||
|
||||
// Expect a readCount of zero if it's end of the stream. But, to enable non-blocking scenario to wait for data, going to treat a negative value as no data.
|
||||
this.lastReadCount = this.uploadStreamInfo.Stream.Read(frame.Payload, 0, (int)Math.Min(maxFrameSize, int.MaxValue));
|
||||
if (this.lastReadCount <= 0)
|
||||
{
|
||||
BufferPool.Release(frame.Payload);
|
||||
frame.Payload = null;
|
||||
frame.PayloadLength = 0;
|
||||
|
||||
if (this.lastReadCount < 0)
|
||||
return;
|
||||
}
|
||||
else
|
||||
frame.PayloadLength = (UInt32)this.lastReadCount;
|
||||
|
||||
frame.PayloadOffset = 0;
|
||||
frame.DontUseMemPool = false;
|
||||
|
||||
if (this.lastReadCount <= 0)
|
||||
{
|
||||
this.uploadStreamInfo.Stream.Dispose();
|
||||
this.uploadStreamInfo = new HTTPRequest.UploadStreamInfo();
|
||||
|
||||
frame.Flags = (byte)(HTTP2DataFlags.END_STREAM);
|
||||
|
||||
this.State = HTTP2StreamStates.HalfClosedLocal;
|
||||
|
||||
this.AssignedRequest.Timing.Add(TimingEventNames.Request_Sent);
|
||||
}
|
||||
|
||||
this.outgoing.Enqueue(frame);
|
||||
|
||||
this.remoteWindow -= frame.PayloadLength;
|
||||
|
||||
this.sentData += frame.PayloadLength;
|
||||
|
||||
if (this.AssignedRequest.OnUploadProgress != null)
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.UploadProgress, this.sentData, this.uploadStreamInfo.Length));
|
||||
}
|
||||
|
||||
protected void OnRemoteSettingChanged(HTTP2SettingsRegistry registry, HTTP2Settings setting, uint oldValue, uint newValue)
|
||||
{
|
||||
switch (setting)
|
||||
{
|
||||
case HTTP2Settings.INITIAL_WINDOW_SIZE:
|
||||
// https://httpwg.org/specs/rfc7540.html#InitialWindowSize
|
||||
// "Prior to receiving a SETTINGS frame that sets a value for SETTINGS_INITIAL_WINDOW_SIZE,
|
||||
// an endpoint can only use the default initial window size when sending flow-controlled frames."
|
||||
// "In addition to changing the flow-control window for streams that are not yet active,
|
||||
// a SETTINGS frame can alter the initial flow-control window size for streams with active flow-control windows
|
||||
// (that is, streams in the "open" or "half-closed (remote)" state). When the value of SETTINGS_INITIAL_WINDOW_SIZE changes,
|
||||
// a receiver MUST adjust the size of all stream flow-control windows that it maintains by the difference between the new value and the old value."
|
||||
|
||||
// So, if we created a stream before the remote peer's initial settings frame is received, we
|
||||
// will adjust the window size. For example: initial window size by default is 65535, if we later
|
||||
// receive a change to 1048576 (1 MB) we will increase the current remoteWindow by (1 048 576 - 65 535 =) 983 041
|
||||
|
||||
// But because initial window size in a setting frame can be smaller then the default 65535 bytes,
|
||||
// the difference can be negative:
|
||||
// "A change to SETTINGS_INITIAL_WINDOW_SIZE can cause the available space in a flow-control window to become negative.
|
||||
// A sender MUST track the negative flow-control window and MUST NOT send new flow-controlled frames
|
||||
// until it receives WINDOW_UPDATE frames that cause the flow-control window to become positive.
|
||||
|
||||
// For example, if the client sends 60 KB immediately on connection establishment
|
||||
// and the server sets the initial window size to be 16 KB, the client will recalculate
|
||||
// the available flow - control window to be - 44 KB on receipt of the SETTINGS frame.
|
||||
// The client retains a negative flow-control window until WINDOW_UPDATE frames restore the
|
||||
// window to being positive, after which the client can resume sending."
|
||||
|
||||
this.remoteWindow += newValue - oldValue;
|
||||
|
||||
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Remote Setting's Initial Window Updated from {1:N0} to {2:N0}, diff: {3:N0}, new remoteWindow: {4:N0}, total data sent: {5:N0}", this.Id, oldValue, newValue, newValue - oldValue, this.remoteWindow, this.sentData), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected static void FinishRequest(HTTP2Stream stream, FramesAsStreamView dataStream)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (dataStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
stream.response.AddData(dataStream);
|
||||
}
|
||||
finally
|
||||
{
|
||||
dataStream.Close();
|
||||
}
|
||||
}
|
||||
|
||||
stream.AssignedRequest.Timing.Add(TimingEventNames.Response_Received);
|
||||
|
||||
bool resendRequest;
|
||||
HTTPConnectionStates proposedConnectionStates; // ignored
|
||||
KeepAliveHeader keepAliveHeader = null; // ignored
|
||||
|
||||
ConnectionHelper.HandleResponse("HTTP2Stream", stream.AssignedRequest, out resendRequest, out proposedConnectionStates, ref keepAliveHeader, stream.Context, stream.AssignedRequest.Context);
|
||||
|
||||
if (resendRequest && !stream.AssignedRequest.IsCancellationRequested)
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(stream.AssignedRequest, RequestEvents.Resend));
|
||||
else if (stream.AssignedRequest.State == HTTPRequestStates.Processing && !stream.AssignedRequest.IsCancellationRequested)
|
||||
stream.AssignedRequest.State = HTTPRequestStates.Finished;
|
||||
else
|
||||
{
|
||||
// Already set in HTTPRequest's Abort().
|
||||
//if (stream.AssignedRequest.State == HTTPRequestStates.Processing && stream.AssignedRequest.IsCancellationRequested)
|
||||
// stream.AssignedRequest.State = stream.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("HTTP2Stream", "FinishRequest", ex, stream.AssignedRequest.Context);
|
||||
}
|
||||
}
|
||||
|
||||
public void Removed()
|
||||
{
|
||||
if (this.uploadStreamInfo.Stream != null)
|
||||
{
|
||||
this.uploadStreamInfo.Stream.Dispose();
|
||||
this.uploadStreamInfo = new HTTPRequest.UploadStreamInfo();
|
||||
}
|
||||
|
||||
// After receiving a RST_STREAM on a stream, the receiver MUST NOT send additional frames for that stream, with the exception of PRIORITY.
|
||||
this.outgoing.Clear();
|
||||
|
||||
// https://github.com/Benedicht/BestHTTP-Issues/issues/77
|
||||
// Unsubscribe from OnSettingChangedEvent to remove reference to this instance.
|
||||
this.settings.RemoteSettings.OnSettingChangedEvent -= OnRemoteSettingChanged;
|
||||
|
||||
HTTPManager.Logger.Information("HTTP2Stream", "Stream removed: " + this.Id.ToString(), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2 && !BESTHTTP_DISABLE_WEBSOCKET
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using BestHTTP.WebSocket;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
public sealed class HTTP2WebSocketStream : HTTP2Stream
|
||||
{
|
||||
public override bool HasFrameToSend
|
||||
{
|
||||
get
|
||||
{
|
||||
// Don't let the connection sleep until
|
||||
return this.outgoing.Count > 0 || // we already booked at least one frame in advance
|
||||
(this.State == HTTP2StreamStates.Open &&
|
||||
this.remoteWindow > 0 &&
|
||||
this.lastReadCount > 0 &&
|
||||
(this.overHTTP2.frames.Count > 0 || this.chunkQueue.Count > 0)); // we are in the middle of sending request data
|
||||
}
|
||||
}
|
||||
|
||||
public override TimeSpan NextInteraction => this.overHTTP2.GetNextInteraction();
|
||||
|
||||
private OverHTTP2 overHTTP2;
|
||||
|
||||
// local list of websocket header-data pairs
|
||||
private List<KeyValuePair<BufferSegment, BufferSegment>> chunkQueue = new List<KeyValuePair<BufferSegment, BufferSegment>>();
|
||||
|
||||
public HTTP2WebSocketStream(uint id, HTTP2Handler parentHandler, HTTP2SettingsManager registry, HPACKEncoder hpackEncoder) : base(id, parentHandler, registry, hpackEncoder)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Assign(HTTPRequest request)
|
||||
{
|
||||
base.Assign(request);
|
||||
|
||||
this.overHTTP2 = request.Tag as OverHTTP2;
|
||||
this.overHTTP2.SetHTTP2Handler(this.parent);
|
||||
}
|
||||
|
||||
protected override void ProcessIncomingDATAFrame(ref HTTP2FrameHeaderAndPayload frame, ref uint windowUpdate)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open)
|
||||
{
|
||||
// ERROR!
|
||||
return;
|
||||
}
|
||||
|
||||
this.downloaded += frame.PayloadLength;
|
||||
|
||||
this.overHTTP2.OnReadThread(frame.Payload.AsBuffer((int)frame.PayloadOffset, (int)frame.PayloadLength));
|
||||
|
||||
// frame's buffer will be released later
|
||||
frame.DontUseMemPool = true;
|
||||
|
||||
// Track received data, and if necessary(local window getting too low), send a window update frame
|
||||
if (this.localWindow < frame.PayloadLength)
|
||||
{
|
||||
HTTPManager.Logger.Error(nameof(HTTP2WebSocketStream), string.Format("[{0}] Frame's PayloadLength ({1:N0}) is larger then local window ({2:N0}). Frame: {3}", this.Id, frame.PayloadLength, this.localWindow, frame), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
}
|
||||
else
|
||||
this.localWindow -= frame.PayloadLength;
|
||||
|
||||
if ((frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0)
|
||||
this.isEndSTRReceived = true;
|
||||
|
||||
if (this.isEndSTRReceived)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(HTTP2WebSocketStream), string.Format("[{0}] All data arrived, data length: {1:N0}", this.Id, this.downloaded), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
|
||||
// create a short living thread to process the downloaded data:
|
||||
PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTP2Stream, FramesAsStreamView>(FinishRequest, this, this.dataView);
|
||||
|
||||
this.dataView = null;
|
||||
|
||||
if (this.State == HTTP2StreamStates.HalfClosedLocal)
|
||||
this.State = HTTP2StreamStates.Closed;
|
||||
else
|
||||
this.State = HTTP2StreamStates.HalfClosedRemote;
|
||||
}
|
||||
|
||||
if (this.isEndSTRReceived || this.localWindow <= this.windowUpdateThreshold)
|
||||
windowUpdate += this.settings.MySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] - this.localWindow - windowUpdate;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception(nameof(HTTP2WebSocketStream), nameof(ProcessIncomingDATAFrame), ex, this.parent.Context);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ProcessOpenState(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
|
||||
{
|
||||
try
|
||||
{
|
||||
// remote Window can be negative! See https://httpwg.org/specs/rfc7540.html#InitialWindowSize
|
||||
if (this.remoteWindow <= 0)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(HTTP2WebSocketStream), string.Format("[{0}] Skipping data sending as remote Window is {1}!", this.Id, this.remoteWindow), this.Context, this.AssignedRequest.Context, this.parent.Context);
|
||||
return;
|
||||
}
|
||||
|
||||
this.overHTTP2.PreReadCallback();
|
||||
|
||||
Int64 maxFragmentSize = Math.Min(BestHTTP.WebSocket.WebSocket.MaxFragmentSize, this.settings.RemoteSettings[HTTP2Settings.MAX_FRAME_SIZE]);
|
||||
Int64 maxFrameSize = Math.Min(maxFragmentSize, this.remoteWindow);
|
||||
|
||||
if (chunkQueue.Count == 0)
|
||||
{
|
||||
if (this.overHTTP2.frames.TryDequeue(out var frame))
|
||||
{
|
||||
this.overHTTP2._bufferedAmount -= (int)frame.Data.Count;
|
||||
|
||||
frame.WriteTo((header, data) => chunkQueue.Add(new KeyValuePair<BufferSegment, BufferSegment>(header, data)), (uint)maxFragmentSize, false, this.Context);
|
||||
}
|
||||
}
|
||||
|
||||
while (this.remoteWindow >= 6 && chunkQueue.Count > 0)
|
||||
{
|
||||
var kvp = chunkQueue[0];
|
||||
|
||||
BufferSegment header = kvp.Key;
|
||||
BufferSegment data = kvp.Value;
|
||||
|
||||
int minBytes = header.Count;
|
||||
int maxBytes = minBytes + data.Count;
|
||||
|
||||
// remote window is less than the minimum we have to send, or
|
||||
// the frame has data but we have space only to send the websocket header
|
||||
if (this.remoteWindow < minBytes || (maxBytes > minBytes && this.remoteWindow == minBytes))
|
||||
return;
|
||||
|
||||
HTTP2FrameHeaderAndPayload headerFrame = new HTTP2FrameHeaderAndPayload();
|
||||
headerFrame.Type = HTTP2FrameTypes.DATA;
|
||||
headerFrame.StreamId = this.Id;
|
||||
headerFrame.PayloadOffset = (uint)header.Offset;
|
||||
headerFrame.PayloadLength = (uint)header.Count;
|
||||
headerFrame.Payload = header.Data;
|
||||
headerFrame.DontUseMemPool = false;
|
||||
|
||||
if (data.Count > 0)
|
||||
{
|
||||
HTTP2FrameHeaderAndPayload dataFrame = new HTTP2FrameHeaderAndPayload();
|
||||
dataFrame.Type = HTTP2FrameTypes.DATA;
|
||||
dataFrame.StreamId = this.Id;
|
||||
|
||||
var buff = data.Slice(data.Offset, (int)Math.Min(data.Count, maxFrameSize));
|
||||
dataFrame.PayloadOffset = (uint)buff.Offset;
|
||||
dataFrame.PayloadLength = (uint)buff.Count;
|
||||
dataFrame.Payload = buff.Data;
|
||||
|
||||
data = data.Slice(buff.Offset + buff.Count);
|
||||
if (data.Count == 0)
|
||||
chunkQueue.RemoveAt(0);
|
||||
else
|
||||
chunkQueue[0] = new KeyValuePair<BufferSegment, BufferSegment>(header, data);
|
||||
|
||||
// release the buffer only with the final frame and with the final frame's last data chunk
|
||||
bool isLast = (header.Data[header.Offset] & 0x80) != 0 /*&& chunkQueue.Count == 0*/;
|
||||
dataFrame.DontUseMemPool = !isLast;
|
||||
|
||||
this.outgoing.Enqueue(headerFrame);
|
||||
this.outgoing.Enqueue(dataFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.outgoing.Enqueue(headerFrame);
|
||||
chunkQueue.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception(nameof(HTTP2WebSocketStream), nameof(ProcessOpenState), ex, this.parent.Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
sealed class HeaderTable
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#static.table.definition
|
||||
// Valid indexes starts with 1, so there's an empty entry.
|
||||
static string[] StaticTableValues = new string[] { string.Empty, string.Empty, "GET", "POST", "/", "/index.html", "http", "https", "200", "204", "206", "304", "400", "404", "500", string.Empty, "gzip, deflate" };
|
||||
|
||||
// https://http2.github.io/http2-spec/compression.html#static.table.definition
|
||||
// Valid indexes starts with 1, so there's an empty entry.
|
||||
static string[] StaticTable = new string[62]
|
||||
{
|
||||
string.Empty,
|
||||
":authority",
|
||||
":method", // GET
|
||||
":method", // POST
|
||||
":path", // /
|
||||
":path", // index.html
|
||||
":scheme", // http
|
||||
":scheme", // https
|
||||
":status", // 200
|
||||
":status", // 204
|
||||
":status", // 206
|
||||
":status", // 304
|
||||
":status", // 400
|
||||
":status", // 404
|
||||
":status", // 500
|
||||
"accept-charset",
|
||||
"accept-encoding", // gzip, deflate
|
||||
"accept-language",
|
||||
"accept-ranges",
|
||||
"accept",
|
||||
"access-control-allow-origin",
|
||||
"age",
|
||||
"allow",
|
||||
"authorization",
|
||||
"cache-control",
|
||||
"content-disposition",
|
||||
"content-encoding",
|
||||
"content-language",
|
||||
"content-length",
|
||||
"content-location",
|
||||
"content-range",
|
||||
"content-type",
|
||||
"cookie",
|
||||
"date",
|
||||
"etag",
|
||||
"expect",
|
||||
"expires",
|
||||
"from",
|
||||
"host",
|
||||
"if-match",
|
||||
"if-modified-since",
|
||||
"if-none-match",
|
||||
"if-range",
|
||||
"if-unmodified-since",
|
||||
"last-modified",
|
||||
"link",
|
||||
"location",
|
||||
"max-forwards",
|
||||
"proxy-authenticate",
|
||||
"proxy-authorization",
|
||||
"range",
|
||||
"referer",
|
||||
"refresh",
|
||||
"retry-after",
|
||||
"server",
|
||||
"set-cookie",
|
||||
"strict-transport-security",
|
||||
"transfer-encoding",
|
||||
"user-agent",
|
||||
"vary",
|
||||
"via",
|
||||
"www-authenticate",
|
||||
};
|
||||
|
||||
public UInt32 DynamicTableSize { get; private set; }
|
||||
public UInt32 MaxDynamicTableSize {
|
||||
get { return this._maxDynamicTableSize; }
|
||||
set
|
||||
{
|
||||
this._maxDynamicTableSize = value;
|
||||
EvictEntries(0);
|
||||
}
|
||||
}
|
||||
private UInt32 _maxDynamicTableSize;
|
||||
|
||||
private List<KeyValuePair<string, string>> DynamicTable = new List<KeyValuePair<string, string>>();
|
||||
private HTTP2SettingsRegistry settingsRegistry;
|
||||
|
||||
public HeaderTable(HTTP2SettingsRegistry registry)
|
||||
{
|
||||
this.settingsRegistry = registry;
|
||||
this.MaxDynamicTableSize = this.settingsRegistry[HTTP2Settings.HEADER_TABLE_SIZE];
|
||||
}
|
||||
|
||||
public KeyValuePair<UInt32, UInt32> GetIndex(string key, string value)
|
||||
{
|
||||
for (int i = 0; i < DynamicTable.Count; ++i)
|
||||
{
|
||||
var kvp = DynamicTable[i];
|
||||
|
||||
// Exact match for both key and value
|
||||
if (kvp.Key.Equals(key, StringComparison.OrdinalIgnoreCase) && kvp.Value.Equals(value, StringComparison.OrdinalIgnoreCase))
|
||||
return new KeyValuePair<UInt32, UInt32>((UInt32)(StaticTable.Length + i), (UInt32)(StaticTable.Length + i));
|
||||
}
|
||||
|
||||
KeyValuePair<UInt32, UInt32> bestMatch = new KeyValuePair<UInt32, UInt32>(0, 0);
|
||||
for (int i = 0; i < StaticTable.Length; ++i)
|
||||
{
|
||||
if (StaticTable[i].Equals(key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (i < StaticTableValues.Length && !string.IsNullOrEmpty(StaticTableValues[i]) && StaticTableValues[i].Equals(value, StringComparison.OrdinalIgnoreCase))
|
||||
return new KeyValuePair<UInt32, UInt32>((UInt32)i, (UInt32)i);
|
||||
else
|
||||
bestMatch = new KeyValuePair<UInt32, UInt32>((UInt32)i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
public string GetKey(UInt32 index)
|
||||
{
|
||||
if (index < StaticTable.Length)
|
||||
return StaticTable[index];
|
||||
|
||||
return this.DynamicTable[(int)(index - StaticTable.Length)].Key;
|
||||
}
|
||||
|
||||
public KeyValuePair<string, string> GetHeader(UInt32 index)
|
||||
{
|
||||
if (index < StaticTable.Length)
|
||||
return new KeyValuePair<string, string>(StaticTable[index],
|
||||
index < StaticTableValues.Length ? StaticTableValues[index] : null);
|
||||
|
||||
return this.DynamicTable[(int)(index - StaticTable.Length)];
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, string> header)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#calculating.table.size
|
||||
// The size of an entry is the sum of its name's length in octets (as defined in Section 5.2),
|
||||
// its value's length in octets, and 32.
|
||||
UInt32 newHeaderSize = CalculateEntrySize(header);
|
||||
|
||||
EvictEntries(newHeaderSize);
|
||||
|
||||
// If the size of the new entry is less than or equal to the maximum size, that entry is added to the table.
|
||||
// It is not an error to attempt to add an entry that is larger than the maximum size;
|
||||
// an attempt to add an entry larger than the maximum size causes the table to be
|
||||
// emptied of all existing entries and results in an empty table.
|
||||
if (this.DynamicTableSize + newHeaderSize <= this.MaxDynamicTableSize)
|
||||
{
|
||||
this.DynamicTable.Insert(0, header);
|
||||
this.DynamicTableSize += (UInt32)newHeaderSize;
|
||||
}
|
||||
}
|
||||
|
||||
private UInt32 CalculateEntrySize(KeyValuePair<string, string> entry)
|
||||
{
|
||||
return 32 + (UInt32)System.Text.Encoding.UTF8.GetByteCount(entry.Key) +
|
||||
(UInt32)System.Text.Encoding.UTF8.GetByteCount(entry.Value);
|
||||
}
|
||||
|
||||
private void EvictEntries(uint newHeaderSize)
|
||||
{
|
||||
// https://http2.github.io/http2-spec/compression.html#entry.addition
|
||||
// Before a new entry is added to the dynamic table, entries are evicted from the end of the dynamic
|
||||
// table until the size of the dynamic table is less than or equal to (maximum size - new entry size) or until the table is empty.
|
||||
while (this.DynamicTableSize + newHeaderSize > this.MaxDynamicTableSize && this.DynamicTable.Count > 0)
|
||||
{
|
||||
KeyValuePair<string, string> entry = this.DynamicTable[this.DynamicTable.Count - 1];
|
||||
this.DynamicTable.RemoveAt(this.DynamicTable.Count - 1);
|
||||
this.DynamicTableSize -= CalculateEntrySize(entry);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
System.Text.StringBuilder sb = PlatformSupport.Text.StringBuilderPool.Get(this.DynamicTable.Count + 3);
|
||||
sb.Append("[HeaderTable ");
|
||||
sb.AppendFormat("DynamicTable count: {0}, DynamicTableSize: {1}, MaxDynamicTableSize: {2}, ", this.DynamicTable.Count, this.DynamicTableSize, this.MaxDynamicTableSize);
|
||||
|
||||
foreach(var kvp in this.DynamicTable)
|
||||
sb.AppendFormat("\"{0}\": \"{1}\", ", kvp.Key, kvp.Value);
|
||||
|
||||
sb.Append("]");
|
||||
return PlatformSupport.Text.StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,917 @@
|
|||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
|
||||
using System;
|
||||
|
||||
namespace BestHTTP.Connections.HTTP2
|
||||
{
|
||||
[BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstruction]
|
||||
static class HuffmanEncoder
|
||||
{
|
||||
public const UInt16 EOS = 256;
|
||||
|
||||
public struct TableEntry
|
||||
{
|
||||
public UInt32 Code;
|
||||
public byte Bits;
|
||||
|
||||
/// <summary>
|
||||
/// It must return 0 or 1 at bit index. Indexing will be relative to the Bits representing the current code. Idx grows from left to right. Idx must be between [1..Bits].
|
||||
/// </summary>
|
||||
public byte GetBitAtIdx(byte idx)
|
||||
{
|
||||
return (byte)((this.Code >> (this.Bits - idx)) & 1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[TableEntry Code: 0x{0:X}, Bits: {1}]", this.Code, this.Bits);
|
||||
}
|
||||
}
|
||||
|
||||
public struct TreeNode
|
||||
{
|
||||
public UInt16 NextZeroIdx;
|
||||
public UInt16 NextOneIdx;
|
||||
|
||||
public UInt16 Value;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[TreeNode Value: {0}, NextZeroIdx: {1}, NextOneIdx: {2}]",
|
||||
this.Value, this.NextZeroIdx, this.NextOneIdx);
|
||||
}
|
||||
}
|
||||
|
||||
static TableEntry[] StaticTable = new TableEntry[257]
|
||||
{
|
||||
new TableEntry{ Code = 0x1ff8 , Bits = 13 },
|
||||
new TableEntry{ Code = 0x7fffd8 , Bits = 23 },
|
||||
new TableEntry{ Code = 0xfffffe2, Bits = 28 },
|
||||
new TableEntry{ Code = 0xfffffe3, Bits = 28 },
|
||||
new TableEntry{ Code = 0xfffffe4, Bits = 28 },
|
||||
new TableEntry{ Code = 0xfffffe5, Bits = 28 },
|
||||
new TableEntry{ Code = 0xfffffe6, Bits = 28 },
|
||||
new TableEntry{ Code = 0xfffffe7, Bits = 28 },
|
||||
new TableEntry{ Code = 0xfffffe8, Bits = 28 },
|
||||
new TableEntry{ Code = 0xffffea, Bits = 24 },
|
||||
new TableEntry{ Code = 0x3ffffffc , Bits = 30 },
|
||||
new TableEntry{ Code = 0xfffffe9 , Bits = 28 },
|
||||
new TableEntry{ Code = 0xfffffea , Bits = 28 },
|
||||
new TableEntry{ Code = 0x3ffffffd , Bits = 30 },
|
||||
new TableEntry{ Code = 0xfffffeb , Bits = 28 },
|
||||
new TableEntry{ Code = 0xfffffec , Bits = 28 },
|
||||
new TableEntry{ Code = 0xfffffed , Bits = 28 },
|
||||
new TableEntry{ Code = 0xfffffee , Bits = 28 },
|
||||
new TableEntry{ Code = 0xfffffef , Bits = 28 },
|
||||
new TableEntry{ Code = 0xffffff0 , Bits = 28 },
|
||||
new TableEntry{ Code = 0xffffff1 , Bits = 28 },
|
||||
new TableEntry{ Code = 0xffffff2 , Bits = 28 },
|
||||
new TableEntry{ Code = 0x3ffffffe, Bits = 30 },
|
||||
new TableEntry{ Code = 0xffffff3 , Bits = 28 },
|
||||
new TableEntry{ Code = 0xffffff4 , Bits = 28 },
|
||||
new TableEntry{ Code = 0xffffff5 , Bits = 28 },
|
||||
new TableEntry{ Code = 0xffffff6 , Bits = 28 },
|
||||
new TableEntry{ Code = 0xffffff7 , Bits = 28 },
|
||||
new TableEntry{ Code = 0xffffff8 , Bits = 28 },
|
||||
new TableEntry{ Code = 0xffffff9 , Bits = 28 },
|
||||
new TableEntry{ Code = 0xffffffa , Bits = 28 },
|
||||
new TableEntry{ Code = 0xffffffb , Bits = 28 },
|
||||
new TableEntry{ Code = 0x14 , Bits = 6 },
|
||||
new TableEntry{ Code = 0x3f8, Bits = 10 },
|
||||
new TableEntry{ Code = 0x3f9, Bits = 10 },
|
||||
new TableEntry{ Code = 0xffa, Bits = 12 },
|
||||
new TableEntry{ Code = 0x1ff9 , Bits = 13 },
|
||||
new TableEntry{ Code = 0x15 , Bits = 6 },
|
||||
new TableEntry{ Code = 0xf8 , Bits = 8 },
|
||||
new TableEntry{ Code = 0x7fa, Bits = 11 },
|
||||
new TableEntry{ Code = 0x3fa, Bits = 10 },
|
||||
new TableEntry{ Code = 0x3fb, Bits = 10 },
|
||||
new TableEntry{ Code = 0xf9 , Bits = 8 },
|
||||
new TableEntry{ Code = 0x7fb, Bits = 11 },
|
||||
new TableEntry{ Code = 0xfa , Bits = 8 },
|
||||
new TableEntry{ Code = 0x16 , Bits = 6 },
|
||||
new TableEntry{ Code = 0x17 , Bits = 6 },
|
||||
new TableEntry{ Code = 0x18 , Bits = 6 },
|
||||
new TableEntry{ Code = 0x0 , Bits = 5 },
|
||||
new TableEntry{ Code = 0x1 , Bits = 5 },
|
||||
new TableEntry{ Code = 0x2 , Bits = 5 },
|
||||
new TableEntry{ Code = 0x19 , Bits = 6 },
|
||||
new TableEntry{ Code = 0x1a , Bits = 6 },
|
||||
new TableEntry{ Code = 0x1b , Bits = 6 },
|
||||
new TableEntry{ Code = 0x1c , Bits = 6 },
|
||||
new TableEntry{ Code = 0x1d , Bits = 6 },
|
||||
new TableEntry{ Code = 0x1e , Bits = 6 },
|
||||
new TableEntry{ Code = 0x1f , Bits = 6 },
|
||||
new TableEntry{ Code = 0x5c , Bits = 7 },
|
||||
new TableEntry{ Code = 0xfb , Bits = 8 },
|
||||
new TableEntry{ Code = 0x7ffc , Bits = 15 },
|
||||
new TableEntry{ Code = 0x20 , Bits = 6 },
|
||||
new TableEntry{ Code = 0xffb, Bits = 12 },
|
||||
new TableEntry{ Code = 0x3fc, Bits = 10 },
|
||||
new TableEntry{ Code = 0x1ffa , Bits = 13 },
|
||||
new TableEntry{ Code = 0x21, Bits = 6 },
|
||||
new TableEntry{ Code = 0x5d, Bits = 7 },
|
||||
new TableEntry{ Code = 0x5e, Bits = 7 },
|
||||
new TableEntry{ Code = 0x5f, Bits = 7 },
|
||||
new TableEntry{ Code = 0x60, Bits = 7 },
|
||||
new TableEntry{ Code = 0x61, Bits = 7 },
|
||||
new TableEntry{ Code = 0x62, Bits = 7 },
|
||||
new TableEntry{ Code = 0x63, Bits = 7 },
|
||||
new TableEntry{ Code = 0x64, Bits = 7 },
|
||||
new TableEntry{ Code = 0x65, Bits = 7 },
|
||||
new TableEntry{ Code = 0x66, Bits = 7 },
|
||||
new TableEntry{ Code = 0x67, Bits = 7 },
|
||||
new TableEntry{ Code = 0x68, Bits = 7 },
|
||||
new TableEntry{ Code = 0x69, Bits = 7 },
|
||||
new TableEntry{ Code = 0x6a, Bits = 7 },
|
||||
new TableEntry{ Code = 0x6b, Bits = 7 },
|
||||
new TableEntry{ Code = 0x6c, Bits = 7 },
|
||||
new TableEntry{ Code = 0x6d, Bits = 7 },
|
||||
new TableEntry{ Code = 0x6e, Bits = 7 },
|
||||
new TableEntry{ Code = 0x6f, Bits = 7 },
|
||||
new TableEntry{ Code = 0x70, Bits = 7 },
|
||||
new TableEntry{ Code = 0x71, Bits = 7 },
|
||||
new TableEntry{ Code = 0x72, Bits = 7 },
|
||||
new TableEntry{ Code = 0xfc, Bits = 8 },
|
||||
new TableEntry{ Code = 0x73, Bits = 7 },
|
||||
new TableEntry{ Code = 0xfd, Bits = 8 },
|
||||
new TableEntry{ Code = 0x1ffb, Bits = 13 },
|
||||
new TableEntry{ Code = 0x7fff0, Bits = 19 },
|
||||
new TableEntry{ Code = 0x1ffc, Bits = 13 },
|
||||
new TableEntry{ Code = 0x3ffc, Bits = 14 },
|
||||
new TableEntry{ Code = 0x22, Bits = 6 },
|
||||
new TableEntry{ Code = 0x7ffd, Bits = 15 },
|
||||
new TableEntry{ Code = 0x3, Bits = 5 },
|
||||
new TableEntry{ Code = 0x23, Bits = 6 },
|
||||
new TableEntry{ Code = 0x4, Bits = 5 },
|
||||
new TableEntry{ Code = 0x24, Bits = 6 },
|
||||
new TableEntry{ Code = 0x5, Bits = 5 },
|
||||
new TableEntry{ Code = 0x25, Bits = 6 },
|
||||
new TableEntry{ Code = 0x26, Bits = 6 },
|
||||
new TableEntry{ Code = 0x27, Bits = 6 },
|
||||
new TableEntry{ Code = 0x6 , Bits = 5 },
|
||||
new TableEntry{ Code = 0x74, Bits = 7 },
|
||||
new TableEntry{ Code = 0x75, Bits = 7 },
|
||||
new TableEntry{ Code = 0x28, Bits = 6 },
|
||||
new TableEntry{ Code = 0x29, Bits = 6 },
|
||||
new TableEntry{ Code = 0x2a, Bits = 6 },
|
||||
new TableEntry{ Code = 0x7 , Bits = 5 },
|
||||
new TableEntry{ Code = 0x2b, Bits = 6 },
|
||||
new TableEntry{ Code = 0x76, Bits = 7 },
|
||||
new TableEntry{ Code = 0x2c, Bits = 6 },
|
||||
new TableEntry{ Code = 0x8 , Bits = 5 },
|
||||
new TableEntry{ Code = 0x9 , Bits = 5 },
|
||||
new TableEntry{ Code = 0x2d, Bits = 6 },
|
||||
new TableEntry{ Code = 0x77, Bits = 7 },
|
||||
new TableEntry{ Code = 0x78, Bits = 7 },
|
||||
new TableEntry{ Code = 0x79, Bits = 7 },
|
||||
new TableEntry{ Code = 0x7a, Bits = 7 },
|
||||
new TableEntry{ Code = 0x7b, Bits = 7 },
|
||||
new TableEntry{ Code = 0x7ffe, Bits = 15 },
|
||||
new TableEntry{ Code = 0x7fc, Bits = 11 },
|
||||
new TableEntry{ Code = 0x3ffd, Bits = 14 },
|
||||
new TableEntry{ Code = 0x1ffd, Bits = 13 },
|
||||
new TableEntry{ Code = 0xffffffc, Bits = 28 },
|
||||
new TableEntry{ Code = 0xfffe6 , Bits = 20 },
|
||||
new TableEntry{ Code = 0x3fffd2, Bits = 22 },
|
||||
new TableEntry{ Code = 0xfffe7 , Bits = 20 },
|
||||
new TableEntry{ Code = 0xfffe8 , Bits = 20 },
|
||||
new TableEntry{ Code = 0x3fffd3, Bits = 22 },
|
||||
new TableEntry{ Code = 0x3fffd4, Bits = 22 },
|
||||
new TableEntry{ Code = 0x3fffd5, Bits = 22 },
|
||||
new TableEntry{ Code = 0x7fffd9, Bits = 23 },
|
||||
new TableEntry{ Code = 0x3fffd6, Bits = 22 },
|
||||
new TableEntry{ Code = 0x7fffda, Bits = 23 },
|
||||
new TableEntry{ Code = 0x7fffdb, Bits = 23 },
|
||||
new TableEntry{ Code = 0x7fffdc, Bits = 23 },
|
||||
new TableEntry{ Code = 0x7fffdd, Bits = 23 },
|
||||
new TableEntry{ Code = 0x7fffde, Bits = 23 },
|
||||
new TableEntry{ Code = 0xffffeb, Bits = 24 },
|
||||
new TableEntry{ Code = 0x7fffdf, Bits = 23 },
|
||||
new TableEntry{ Code = 0xffffec, Bits = 24 },
|
||||
new TableEntry{ Code = 0xffffed, Bits = 24 },
|
||||
new TableEntry{ Code = 0x3fffd7, Bits = 22 },
|
||||
new TableEntry{ Code = 0x7fffe0, Bits = 23 },
|
||||
new TableEntry{ Code = 0xffffee, Bits = 24 },
|
||||
new TableEntry{ Code = 0x7fffe1, Bits = 23 },
|
||||
new TableEntry{ Code = 0x7fffe2, Bits = 23 },
|
||||
new TableEntry{ Code = 0x7fffe3, Bits = 23 },
|
||||
new TableEntry{ Code = 0x7fffe4, Bits = 23 },
|
||||
new TableEntry{ Code = 0x1fffdc, Bits = 21 },
|
||||
new TableEntry{ Code = 0x3fffd8, Bits = 22 },
|
||||
new TableEntry{ Code = 0x7fffe5, Bits = 23 },
|
||||
new TableEntry{ Code = 0x3fffd9, Bits = 22 },
|
||||
new TableEntry{ Code = 0x7fffe6, Bits = 23 },
|
||||
new TableEntry{ Code = 0x7fffe7, Bits = 23 },
|
||||
new TableEntry{ Code = 0xffffef, Bits = 24 },
|
||||
new TableEntry{ Code = 0x3fffda, Bits = 22 },
|
||||
new TableEntry{ Code = 0x1fffdd, Bits = 21 },
|
||||
new TableEntry{ Code = 0xfffe9 , Bits = 20 },
|
||||
new TableEntry{ Code = 0x3fffdb, Bits = 22 },
|
||||
new TableEntry{ Code = 0x3fffdc, Bits = 22 },
|
||||
new TableEntry{ Code = 0x7fffe8, Bits = 23 },
|
||||
new TableEntry{ Code = 0x7fffe9, Bits = 23 },
|
||||
new TableEntry{ Code = 0x1fffde, Bits = 21 },
|
||||
new TableEntry{ Code = 0x7fffea, Bits = 23 },
|
||||
new TableEntry{ Code = 0x3fffdd, Bits = 22 },
|
||||
new TableEntry{ Code = 0x3fffde, Bits = 22 },
|
||||
new TableEntry{ Code = 0xfffff0, Bits = 24 },
|
||||
new TableEntry{ Code = 0x1fffdf, Bits = 21 },
|
||||
new TableEntry{ Code = 0x3fffdf, Bits = 22 },
|
||||
new TableEntry{ Code = 0x7fffeb, Bits = 23 },
|
||||
new TableEntry{ Code = 0x7fffec, Bits = 23 },
|
||||
new TableEntry{ Code = 0x1fffe0, Bits = 21 },
|
||||
new TableEntry{ Code = 0x1fffe1, Bits = 21 },
|
||||
new TableEntry{ Code = 0x3fffe0, Bits = 22 },
|
||||
new TableEntry{ Code = 0x1fffe2, Bits = 21 },
|
||||
new TableEntry{ Code = 0x7fffed, Bits = 23 },
|
||||
new TableEntry{ Code = 0x3fffe1, Bits = 22 },
|
||||
new TableEntry{ Code = 0x7fffee, Bits = 23 },
|
||||
new TableEntry{ Code = 0x7fffef, Bits = 23 },
|
||||
new TableEntry{ Code = 0xfffea , Bits = 20 },
|
||||
new TableEntry{ Code = 0x3fffe2, Bits = 22 },
|
||||
new TableEntry{ Code = 0x3fffe3, Bits = 22 },
|
||||
new TableEntry{ Code = 0x3fffe4, Bits = 22 },
|
||||
new TableEntry{ Code = 0x7ffff0, Bits = 23 },
|
||||
new TableEntry{ Code = 0x3fffe5, Bits = 22 },
|
||||
new TableEntry{ Code = 0x3fffe6, Bits = 22 },
|
||||
new TableEntry{ Code = 0x7ffff1, Bits = 23 },
|
||||
new TableEntry{ Code = 0x3ffffe0, Bits = 26 },
|
||||
new TableEntry{ Code = 0x3ffffe1, Bits = 26 },
|
||||
new TableEntry{ Code = 0xfffeb , Bits = 20 },
|
||||
new TableEntry{ Code = 0x7fff1 , Bits = 19 },
|
||||
new TableEntry{ Code = 0x3fffe7, Bits = 22 },
|
||||
new TableEntry{ Code = 0x7ffff2, Bits = 23 },
|
||||
new TableEntry{ Code = 0x3fffe8, Bits = 22 },
|
||||
new TableEntry{ Code = 0x1ffffec, Bits = 25 },
|
||||
new TableEntry{ Code = 0x3ffffe2, Bits = 26 },
|
||||
new TableEntry{ Code = 0x3ffffe3, Bits = 26 },
|
||||
new TableEntry{ Code = 0x3ffffe4, Bits = 26 },
|
||||
new TableEntry{ Code = 0x7ffffde, Bits = 27 },
|
||||
new TableEntry{ Code = 0x7ffffdf, Bits = 27 },
|
||||
new TableEntry{ Code = 0x3ffffe5, Bits = 26 },
|
||||
new TableEntry{ Code = 0xfffff1 , Bits = 24 },
|
||||
new TableEntry{ Code = 0x1ffffed, Bits = 25 },
|
||||
new TableEntry{ Code = 0x7fff2 , Bits = 19 },
|
||||
new TableEntry{ Code = 0x1fffe3 , Bits = 21 },
|
||||
new TableEntry{ Code = 0x3ffffe6, Bits = 26 },
|
||||
new TableEntry{ Code = 0x7ffffe0, Bits = 27 },
|
||||
new TableEntry{ Code = 0x7ffffe1, Bits = 27 },
|
||||
new TableEntry{ Code = 0x3ffffe7, Bits = 26 },
|
||||
new TableEntry{ Code = 0x7ffffe2, Bits = 27 },
|
||||
new TableEntry{ Code = 0xfffff2 , Bits = 24 },
|
||||
new TableEntry{ Code = 0x1fffe4 , Bits = 21 },
|
||||
new TableEntry{ Code = 0x1fffe5 , Bits = 21 },
|
||||
new TableEntry{ Code = 0x3ffffe8, Bits = 26 },
|
||||
new TableEntry{ Code = 0x3ffffe9, Bits = 26 },
|
||||
new TableEntry{ Code = 0xffffffd, Bits = 28 },
|
||||
new TableEntry{ Code = 0x7ffffe3, Bits = 27 },
|
||||
new TableEntry{ Code = 0x7ffffe4, Bits = 27 },
|
||||
new TableEntry{ Code = 0x7ffffe5, Bits = 27 },
|
||||
new TableEntry{ Code = 0xfffec , Bits = 20 },
|
||||
new TableEntry{ Code = 0xfffff3, Bits = 24 },
|
||||
new TableEntry{ Code = 0xfffed , Bits = 20 },
|
||||
new TableEntry{ Code = 0x1fffe6, Bits = 21 },
|
||||
new TableEntry{ Code = 0x3fffe9, Bits = 22 },
|
||||
new TableEntry{ Code = 0x1fffe7, Bits = 21 },
|
||||
new TableEntry{ Code = 0x1fffe8, Bits = 21 },
|
||||
new TableEntry{ Code = 0x7ffff3, Bits = 23 },
|
||||
new TableEntry{ Code = 0x3fffea, Bits = 22 },
|
||||
new TableEntry{ Code = 0x3fffeb, Bits = 22 },
|
||||
new TableEntry{ Code = 0x1ffffee, Bits = 25 },
|
||||
new TableEntry{ Code = 0x1ffffef, Bits = 25 },
|
||||
new TableEntry{ Code = 0xfffff4 , Bits = 24 },
|
||||
new TableEntry{ Code = 0xfffff5 , Bits = 24 },
|
||||
new TableEntry{ Code = 0x3ffffea, Bits = 26 },
|
||||
new TableEntry{ Code = 0x7ffff4 , Bits = 23 },
|
||||
new TableEntry{ Code = 0x3ffffeb, Bits = 26 },
|
||||
new TableEntry{ Code = 0x7ffffe6, Bits = 27 },
|
||||
new TableEntry{ Code = 0x3ffffec, Bits = 26 },
|
||||
new TableEntry{ Code = 0x3ffffed, Bits = 26 },
|
||||
new TableEntry{ Code = 0x7ffffe7, Bits = 27 },
|
||||
new TableEntry{ Code = 0x7ffffe8, Bits = 27 },
|
||||
new TableEntry{ Code = 0x7ffffe9, Bits = 27 },
|
||||
new TableEntry{ Code = 0x7ffffea, Bits = 27 },
|
||||
new TableEntry{ Code = 0x7ffffeb, Bits = 27 },
|
||||
new TableEntry{ Code = 0xffffffe, Bits = 28 },
|
||||
new TableEntry{ Code = 0x7ffffec, Bits = 27 },
|
||||
new TableEntry{ Code = 0x7ffffed, Bits = 27 },
|
||||
new TableEntry{ Code = 0x7ffffee, Bits = 27 },
|
||||
new TableEntry{ Code = 0x7ffffef, Bits = 27 },
|
||||
new TableEntry{ Code = 0x7fffff0, Bits = 27 },
|
||||
new TableEntry{ Code = 0x3ffffee, Bits = 26 },
|
||||
new TableEntry{ Code = 0x3fffffff, Bits = 30 }
|
||||
};
|
||||
//static List<TreeNode> entries = new List<TreeNode>();
|
||||
|
||||
static TreeNode[] HuffmanTree = new TreeNode[]
|
||||
{
|
||||
new TreeNode { Value = 0, NextZeroIdx = 98, NextOneIdx = 1 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 151, NextOneIdx = 2 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 173, NextOneIdx = 3 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 204, NextOneIdx = 4 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 263, NextOneIdx = 5 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 113, NextOneIdx = 6 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 211, NextOneIdx = 7 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 104, NextOneIdx = 8 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 116, NextOneIdx = 9 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 108, NextOneIdx = 10 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 11, NextOneIdx = 14 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 12, NextOneIdx = 166 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 13, NextOneIdx = 111 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 220, NextOneIdx = 15 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 222, NextOneIdx = 16 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 158, NextOneIdx = 17 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 270, NextOneIdx = 18 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 216, NextOneIdx = 19 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 279, NextOneIdx = 20 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 21, NextOneIdx = 27 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 377, NextOneIdx = 22 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 414, NextOneIdx = 23 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 24, NextOneIdx = 301 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 25, NextOneIdx = 298 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 26, NextOneIdx = 295 },
|
||||
new TreeNode { Value = 1, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 314, NextOneIdx = 28 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 50, NextOneIdx = 29 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 362, NextOneIdx = 30 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 403, NextOneIdx = 31 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 440, NextOneIdx = 32 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 33, NextOneIdx = 55 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 34, NextOneIdx = 46 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 35, NextOneIdx = 39 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 510, NextOneIdx = 36 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 37, NextOneIdx = 38 },
|
||||
new TreeNode { Value = 2, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 3, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 40, NextOneIdx = 43 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 41, NextOneIdx = 42 },
|
||||
new TreeNode { Value = 4, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 5, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 44, NextOneIdx = 45 },
|
||||
new TreeNode { Value = 6, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 7, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 47, NextOneIdx = 67 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 48, NextOneIdx = 63 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 49, NextOneIdx = 62 },
|
||||
new TreeNode { Value = 8, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 396, NextOneIdx = 51 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 52, NextOneIdx = 309 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 486, NextOneIdx = 53 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 54, NextOneIdx = 307 },
|
||||
new TreeNode { Value = 9, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 74, NextOneIdx = 56 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 91, NextOneIdx = 57 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 274, NextOneIdx = 58 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 502, NextOneIdx = 59 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 60, NextOneIdx = 81 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 61, NextOneIdx = 65 },
|
||||
new TreeNode { Value = 10, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 11, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 64, NextOneIdx = 66 },
|
||||
new TreeNode { Value = 12, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 13, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 14, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 68, NextOneIdx = 71 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 69, NextOneIdx = 70 },
|
||||
new TreeNode { Value = 15, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 16, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 72, NextOneIdx = 73 },
|
||||
new TreeNode { Value = 17, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 18, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 75, NextOneIdx = 84 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 76, NextOneIdx = 79 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 77, NextOneIdx = 78 },
|
||||
new TreeNode { Value = 19, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 20, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 80, NextOneIdx = 83 },
|
||||
new TreeNode { Value = 21, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 82, NextOneIdx = 512 },
|
||||
new TreeNode { Value = 22, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 23, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 85, NextOneIdx = 88 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 86, NextOneIdx = 87 },
|
||||
new TreeNode { Value = 24, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 25, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 89, NextOneIdx = 90 },
|
||||
new TreeNode { Value = 26, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 27, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 92, NextOneIdx = 95 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 93, NextOneIdx = 94 },
|
||||
new TreeNode { Value = 28, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 29, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 96, NextOneIdx = 97 },
|
||||
new TreeNode { Value = 30, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 31, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 133, NextOneIdx = 99 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 100, NextOneIdx = 129 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 258, NextOneIdx = 101 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 102, NextOneIdx = 126 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 103, NextOneIdx = 112 },
|
||||
new TreeNode { Value = 32, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 105, NextOneIdx = 119 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 106, NextOneIdx = 107 },
|
||||
new TreeNode { Value = 33, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 34, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 271, NextOneIdx = 109 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 110, NextOneIdx = 164 },
|
||||
new TreeNode { Value = 35, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 36, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 37, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 114, NextOneIdx = 124 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 115, NextOneIdx = 122 },
|
||||
new TreeNode { Value = 38, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 165, NextOneIdx = 117 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 118, NextOneIdx = 123 },
|
||||
new TreeNode { Value = 39, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 120, NextOneIdx = 121 },
|
||||
new TreeNode { Value = 40, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 41, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 42, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 43, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 125, NextOneIdx = 157 },
|
||||
new TreeNode { Value = 44, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 127, NextOneIdx = 128 },
|
||||
new TreeNode { Value = 45, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 46, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 130, NextOneIdx = 144 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 131, NextOneIdx = 141 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 132, NextOneIdx = 140 },
|
||||
new TreeNode { Value = 47, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 134, NextOneIdx = 229 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 135, NextOneIdx = 138 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 136, NextOneIdx = 137 },
|
||||
new TreeNode { Value = 48, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 49, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 139, NextOneIdx = 227 },
|
||||
new TreeNode { Value = 50, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 51, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 142, NextOneIdx = 143 },
|
||||
new TreeNode { Value = 52, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 53, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 145, NextOneIdx = 148 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 146, NextOneIdx = 147 },
|
||||
new TreeNode { Value = 54, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 55, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 149, NextOneIdx = 150 },
|
||||
new TreeNode { Value = 56, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 57, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 160, NextOneIdx = 152 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 246, NextOneIdx = 153 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 256, NextOneIdx = 154 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 155, NextOneIdx = 170 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 156, NextOneIdx = 169 },
|
||||
new TreeNode { Value = 58, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 59, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 159, NextOneIdx = 226 },
|
||||
new TreeNode { Value = 60, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 161, NextOneIdx = 232 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 162, NextOneIdx = 224 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 163, NextOneIdx = 168 },
|
||||
new TreeNode { Value = 61, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 62, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 63, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 167, NextOneIdx = 215 },
|
||||
new TreeNode { Value = 64, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 65, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 66, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 171, NextOneIdx = 172 },
|
||||
new TreeNode { Value = 67, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 68, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 174, NextOneIdx = 189 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 175, NextOneIdx = 182 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 176, NextOneIdx = 179 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 177, NextOneIdx = 178 },
|
||||
new TreeNode { Value = 69, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 70, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 180, NextOneIdx = 181 },
|
||||
new TreeNode { Value = 71, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 72, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 183, NextOneIdx = 186 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 184, NextOneIdx = 185 },
|
||||
new TreeNode { Value = 73, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 74, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 187, NextOneIdx = 188 },
|
||||
new TreeNode { Value = 75, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 76, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 190, NextOneIdx = 197 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 191, NextOneIdx = 194 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 192, NextOneIdx = 193 },
|
||||
new TreeNode { Value = 77, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 78, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 195, NextOneIdx = 196 },
|
||||
new TreeNode { Value = 79, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 80, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 198, NextOneIdx = 201 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 199, NextOneIdx = 200 },
|
||||
new TreeNode { Value = 81, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 82, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 202, NextOneIdx = 203 },
|
||||
new TreeNode { Value = 83, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 84, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 205, NextOneIdx = 242 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 206, NextOneIdx = 209 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 207, NextOneIdx = 208 },
|
||||
new TreeNode { Value = 85, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 86, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 210, NextOneIdx = 213 },
|
||||
new TreeNode { Value = 87, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 212, NextOneIdx = 214 },
|
||||
new TreeNode { Value = 88, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 89, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 90, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 91, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 217, NextOneIdx = 286 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 218, NextOneIdx = 276 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 219, NextOneIdx = 410 },
|
||||
new TreeNode { Value = 92, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 221, NextOneIdx = 273 },
|
||||
new TreeNode { Value = 93, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 223, NextOneIdx = 272 },
|
||||
new TreeNode { Value = 94, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 225, NextOneIdx = 228 },
|
||||
new TreeNode { Value = 95, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 96, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 97, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 98, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 230, NextOneIdx = 240 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 231, NextOneIdx = 235 },
|
||||
new TreeNode { Value = 99, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 233, NextOneIdx = 237 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 234, NextOneIdx = 236 },
|
||||
new TreeNode { Value = 100, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 101, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 102, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 238, NextOneIdx = 239 },
|
||||
new TreeNode { Value = 103, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 104, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 241, NextOneIdx = 252 },
|
||||
new TreeNode { Value = 105, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 243, NextOneIdx = 254 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 244, NextOneIdx = 245 },
|
||||
new TreeNode { Value = 106, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 107, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 247, NextOneIdx = 250 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 248, NextOneIdx = 249 },
|
||||
new TreeNode { Value = 108, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 109, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 251, NextOneIdx = 253 },
|
||||
new TreeNode { Value = 110, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 111, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 112, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 255, NextOneIdx = 262 },
|
||||
new TreeNode { Value = 113, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 257, NextOneIdx = 261 },
|
||||
new TreeNode { Value = 114, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 259, NextOneIdx = 260 },
|
||||
new TreeNode { Value = 115, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 116, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 117, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 118, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 264, NextOneIdx = 267 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 265, NextOneIdx = 266 },
|
||||
new TreeNode { Value = 119, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 120, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 268, NextOneIdx = 269 },
|
||||
new TreeNode { Value = 121, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 122, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 123, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 124, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 125, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 126, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 275, NextOneIdx = 459 },
|
||||
new TreeNode { Value = 127, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 436, NextOneIdx = 277 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 278, NextOneIdx = 285 },
|
||||
new TreeNode { Value = 128, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 372, NextOneIdx = 280 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 281, NextOneIdx = 332 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 282, NextOneIdx = 291 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 473, NextOneIdx = 283 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 284, NextOneIdx = 290 },
|
||||
new TreeNode { Value = 129, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 130, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 287, NextOneIdx = 328 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 288, NextOneIdx = 388 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 289, NextOneIdx = 345 },
|
||||
new TreeNode { Value = 131, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 132, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 292, NextOneIdx = 296 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 293, NextOneIdx = 294 },
|
||||
new TreeNode { Value = 133, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 134, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 135, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 297, NextOneIdx = 313 },
|
||||
new TreeNode { Value = 136, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 299, NextOneIdx = 300 },
|
||||
new TreeNode { Value = 137, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 138, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 302, NextOneIdx = 305 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 303, NextOneIdx = 304 },
|
||||
new TreeNode { Value = 139, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 140, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 306, NextOneIdx = 308 },
|
||||
new TreeNode { Value = 141, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 142, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 143, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 310, NextOneIdx = 319 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 311, NextOneIdx = 312 },
|
||||
new TreeNode { Value = 144, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 145, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 146, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 315, NextOneIdx = 350 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 316, NextOneIdx = 325 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 317, NextOneIdx = 322 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 318, NextOneIdx = 321 },
|
||||
new TreeNode { Value = 147, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 320, NextOneIdx = 341 },
|
||||
new TreeNode { Value = 148, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 149, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 323, NextOneIdx = 324 },
|
||||
new TreeNode { Value = 150, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 151, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 326, NextOneIdx = 338 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 327, NextOneIdx = 336 },
|
||||
new TreeNode { Value = 152, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 465, NextOneIdx = 329 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 330, NextOneIdx = 355 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 331, NextOneIdx = 344 },
|
||||
new TreeNode { Value = 153, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 333, NextOneIdx = 347 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 334, NextOneIdx = 342 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 335, NextOneIdx = 337 },
|
||||
new TreeNode { Value = 154, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 155, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 156, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 339, NextOneIdx = 340 },
|
||||
new TreeNode { Value = 157, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 158, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 159, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 343, NextOneIdx = 346 },
|
||||
new TreeNode { Value = 160, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 161, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 162, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 163, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 348, NextOneIdx = 360 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 349, NextOneIdx = 359 },
|
||||
new TreeNode { Value = 164, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 351, NextOneIdx = 369 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 352, NextOneIdx = 357 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 353, NextOneIdx = 354 },
|
||||
new TreeNode { Value = 165, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 166, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 356, NextOneIdx = 366 },
|
||||
new TreeNode { Value = 167, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 358, NextOneIdx = 368 },
|
||||
new TreeNode { Value = 168, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 169, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 361, NextOneIdx = 367 },
|
||||
new TreeNode { Value = 170, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 363, NextOneIdx = 417 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 364, NextOneIdx = 449 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 365, NextOneIdx = 434 },
|
||||
new TreeNode { Value = 171, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 172, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 173, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 174, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 370, NextOneIdx = 385 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 371, NextOneIdx = 383 },
|
||||
new TreeNode { Value = 175, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 373, NextOneIdx = 451 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 374, NextOneIdx = 381 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 375, NextOneIdx = 376 },
|
||||
new TreeNode { Value = 176, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 177, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 378, NextOneIdx = 393 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 379, NextOneIdx = 390 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 380, NextOneIdx = 384 },
|
||||
new TreeNode { Value = 178, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 382, NextOneIdx = 437 },
|
||||
new TreeNode { Value = 179, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 180, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 181, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 386, NextOneIdx = 387 },
|
||||
new TreeNode { Value = 182, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 183, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 389, NextOneIdx = 409 },
|
||||
new TreeNode { Value = 184, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 391, NextOneIdx = 392 },
|
||||
new TreeNode { Value = 185, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 186, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 394, NextOneIdx = 400 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 395, NextOneIdx = 399 },
|
||||
new TreeNode { Value = 187, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 397, NextOneIdx = 412 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 398, NextOneIdx = 402 },
|
||||
new TreeNode { Value = 188, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 189, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 401, NextOneIdx = 411 },
|
||||
new TreeNode { Value = 190, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 191, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 404, NextOneIdx = 427 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 405, NextOneIdx = 424 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 406, NextOneIdx = 421 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 407, NextOneIdx = 408 },
|
||||
new TreeNode { Value = 192, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 193, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 194, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 195, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 196, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 413, NextOneIdx = 474 },
|
||||
new TreeNode { Value = 197, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 415, NextOneIdx = 475 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 416, NextOneIdx = 471 },
|
||||
new TreeNode { Value = 198, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 481, NextOneIdx = 418 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 419, NextOneIdx = 478 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 420, NextOneIdx = 435 },
|
||||
new TreeNode { Value = 199, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 422, NextOneIdx = 423 },
|
||||
new TreeNode { Value = 200, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 201, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 425, NextOneIdx = 438 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 426, NextOneIdx = 433 },
|
||||
new TreeNode { Value = 202, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 455, NextOneIdx = 428 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 490, NextOneIdx = 429 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 511, NextOneIdx = 430 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 431, NextOneIdx = 432 },
|
||||
new TreeNode { Value = 203, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 204, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 205, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 206, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 207, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 208, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 209, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 439, NextOneIdx = 446 },
|
||||
new TreeNode { Value = 210, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 441, NextOneIdx = 494 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 442, NextOneIdx = 461 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 443, NextOneIdx = 447 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 444, NextOneIdx = 445 },
|
||||
new TreeNode { Value = 211, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 212, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 213, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 448, NextOneIdx = 460 },
|
||||
new TreeNode { Value = 214, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 450, NextOneIdx = 467 },
|
||||
new TreeNode { Value = 215, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 452, NextOneIdx = 469 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 453, NextOneIdx = 454 },
|
||||
new TreeNode { Value = 216, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 217, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 456, NextOneIdx = 484 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 457, NextOneIdx = 458 },
|
||||
new TreeNode { Value = 218, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 219, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 220, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 221, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 462, NextOneIdx = 488 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 463, NextOneIdx = 464 },
|
||||
new TreeNode { Value = 222, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 223, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 466, NextOneIdx = 468 },
|
||||
new TreeNode { Value = 224, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 225, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 226, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 470, NextOneIdx = 472 },
|
||||
new TreeNode { Value = 227, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 228, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 229, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 230, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 231, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 476, NextOneIdx = 477 },
|
||||
new TreeNode { Value = 232, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 233, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 479, NextOneIdx = 480 },
|
||||
new TreeNode { Value = 234, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 235, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 482, NextOneIdx = 483 },
|
||||
new TreeNode { Value = 236, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 237, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 485, NextOneIdx = 487 },
|
||||
new TreeNode { Value = 238, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 239, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 240, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 489, NextOneIdx = 493 },
|
||||
new TreeNode { Value = 241, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 491, NextOneIdx = 492 },
|
||||
new TreeNode { Value = 242, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 243, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 244, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 495, NextOneIdx = 503 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 496, NextOneIdx = 499 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 497, NextOneIdx = 498 },
|
||||
new TreeNode { Value = 245, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 246, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 500, NextOneIdx = 501 },
|
||||
new TreeNode { Value = 247, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 248, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 249, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 504, NextOneIdx = 507 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 505, NextOneIdx = 506 },
|
||||
new TreeNode { Value = 250, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 251, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 0, NextZeroIdx = 508, NextOneIdx = 509 },
|
||||
new TreeNode { Value = 252, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 253, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 254, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 255, NextZeroIdx = 0, NextOneIdx = 0 },
|
||||
new TreeNode { Value = 256, NextZeroIdx = 0, NextOneIdx = 0 }
|
||||
};
|
||||
|
||||
//static HuffmanEncoder()
|
||||
//{
|
||||
// BuildTree();
|
||||
//}
|
||||
//
|
||||
//private static void BuildTree()
|
||||
//{
|
||||
// // Add root
|
||||
// entries.Add(new TreeNode());
|
||||
//
|
||||
// for (int i = 0; i < StaticTable.Length; ++i)
|
||||
// {
|
||||
// var tableEntry = StaticTable[i];
|
||||
// var currentNode = entries[0];
|
||||
// int currentNodeIdx = 0;
|
||||
//
|
||||
// for (byte bitIdx = 1; bitIdx <= tableEntry.Bits; bitIdx++)
|
||||
// {
|
||||
// byte bit = tableEntry.GetBitAtIdx(bitIdx);
|
||||
//
|
||||
// switch(bit)
|
||||
// {
|
||||
// case 0:
|
||||
// if (currentNode.NextZeroIdx == 0)
|
||||
// {
|
||||
// currentNode.NextZeroIdx = (UInt16)entries.Count;
|
||||
// entries[currentNodeIdx] = currentNode;
|
||||
// entries.Add(new TreeNode());
|
||||
// }
|
||||
//
|
||||
// currentNodeIdx = currentNode.NextZeroIdx;
|
||||
// currentNode = entries[currentNodeIdx];
|
||||
// break;
|
||||
//
|
||||
// case 1:
|
||||
// if (currentNode.NextOneIdx == 0)
|
||||
// {
|
||||
// currentNode.NextOneIdx = (UInt16)entries.Count;
|
||||
// entries[currentNodeIdx] = currentNode;
|
||||
// entries.Add(new TreeNode());
|
||||
// }
|
||||
//
|
||||
// currentNodeIdx = currentNode.NextOneIdx;
|
||||
// currentNode = entries[currentNodeIdx];
|
||||
// break;
|
||||
//
|
||||
// default:
|
||||
// HTTPManager.Logger.Information("HuffmanEncoder", "BuildTree - GetBitAtIdx returned with an unsupported value: " + bit);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// entries[currentNodeIdx] = new TreeNode { Value = (UInt16)i };
|
||||
//
|
||||
// //HTTPManager.Logger.Information("HuffmanEncoder", string.Format("BuildTree - {0} - Entry({1}) added to idx: {2}", i, entries[currentNodeIdx], currentNodeIdx));
|
||||
// }
|
||||
//
|
||||
// //HTTPManager.Logger.Information("HuffmanEncoder", "BuildTree - entries: " + entries.Count);
|
||||
// //for (int i = 0; i < entries.Count; ++i)
|
||||
// // HTTPManager.Logger.Information("HuffmanEncoder", string.Format("{0} - Entry : {1}", i, entries[i]));
|
||||
// System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
// for (int i = 0; i < entries.Count; ++i)
|
||||
// {
|
||||
// sb.AppendFormat("new TreeNode {{ Value = {0}, NextZeroIdx = {1}, NextOneIdx = {2} }},\n", entries[i].Value, entries[i].NextZeroIdx, entries[i].NextOneIdx);
|
||||
// }
|
||||
// UnityEngine.Debug.Log(sb.ToString());
|
||||
//}
|
||||
|
||||
public static TreeNode GetRoot()
|
||||
{
|
||||
return HuffmanTree[0];
|
||||
}
|
||||
|
||||
public static TreeNode GetNext(TreeNode current, byte bit)
|
||||
{
|
||||
switch(bit)
|
||||
{
|
||||
case 0:
|
||||
return HuffmanTree[current.NextZeroIdx];
|
||||
case 1:
|
||||
return HuffmanTree[current.NextOneIdx];
|
||||
}
|
||||
|
||||
throw new Exception("HuffmanEncoder - GetNext - unsupported bit: " + bit);
|
||||
}
|
||||
|
||||
public static TableEntry GetEntryForCodePoint(UInt16 codePoint)
|
||||
{
|
||||
return StaticTable[codePoint];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls;
|
||||
#endif
|
||||
|
||||
using BestHTTP.Core;
|
||||
using BestHTTP.Timings;
|
||||
|
||||
namespace BestHTTP.Connections
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents and manages a connection to a server.
|
||||
/// </summary>
|
||||
public sealed class HTTPConnection : ConnectionBase
|
||||
{
|
||||
public TCPConnector connector;
|
||||
public IHTTPRequestHandler requestHandler;
|
||||
|
||||
public override TimeSpan KeepAliveTime {
|
||||
get {
|
||||
if (this.requestHandler != null && this.requestHandler.KeepAlive != null)
|
||||
{
|
||||
if (this.requestHandler.KeepAlive.MaxRequests > 0)
|
||||
{
|
||||
if (base.KeepAliveTime < this.requestHandler.KeepAlive.TimeOut)
|
||||
return base.KeepAliveTime;
|
||||
else
|
||||
return this.requestHandler.KeepAlive.TimeOut;
|
||||
}
|
||||
else
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
return base.KeepAliveTime;
|
||||
}
|
||||
|
||||
protected set
|
||||
{
|
||||
base.KeepAliveTime = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanProcessMultiple
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.requestHandler != null)
|
||||
return this.requestHandler.CanProcessMultiple;
|
||||
return base.CanProcessMultiple;
|
||||
}
|
||||
}
|
||||
|
||||
internal HTTPConnection(string serverAddress)
|
||||
:base(serverAddress)
|
||||
{}
|
||||
|
||||
public override bool TestConnection()
|
||||
{
|
||||
#if !NETFX_CORE
|
||||
try
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
TlsStream stream = (this.connector?.Stream as TlsStream);
|
||||
if (stream != null && stream.Protocol != null)
|
||||
{
|
||||
bool locked = stream.Protocol.TryEnterApplicationDataLock(0);
|
||||
try
|
||||
{
|
||||
if (locked && this.connector.Client.Available > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var available = stream.Protocol.TestApplicationData();
|
||||
return !stream.Protocol.IsClosed;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (locked)
|
||||
stream.Protocol.ExitApplicationDataLock();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool connected = this.connector.Client.Connected;
|
||||
|
||||
return connected;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return base.TestConnection();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal override void Process(HTTPRequest request)
|
||||
{
|
||||
this.LastProcessedUri = request.CurrentUri;
|
||||
|
||||
if (this.requestHandler == null || !this.requestHandler.HasCustomRequestProcessor)
|
||||
base.Process(request);
|
||||
else
|
||||
{
|
||||
this.requestHandler.Process(request);
|
||||
LastProcessTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ThreadFunc()
|
||||
{
|
||||
if (this.CurrentRequest.IsRedirected)
|
||||
this.CurrentRequest.Timing.Add(TimingEventNames.Queued_For_Redirection);
|
||||
else
|
||||
this.CurrentRequest.Timing.Add(TimingEventNames.Queued);
|
||||
|
||||
if (this.connector != null && !this.connector.IsConnected)
|
||||
{
|
||||
// this will send the request back to the queue
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(CurrentRequest, RequestEvents.Resend));
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.connector == null)
|
||||
{
|
||||
this.connector = new Connections.TCPConnector();
|
||||
|
||||
try
|
||||
{
|
||||
this.connector.Connect(this.CurrentRequest);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Exception("HTTPConnection", "Connector.Connect", ex, this.Context, this.CurrentRequest.Context);
|
||||
|
||||
|
||||
if (ex is TimeoutException)
|
||||
this.CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut;
|
||||
else if (!this.CurrentRequest.IsTimedOut) // Do nothing here if Abort() got called on the request, its State is already set.
|
||||
{
|
||||
this.CurrentRequest.Exception = ex;
|
||||
this.CurrentRequest.State = HTTPRequestStates.Error;
|
||||
}
|
||||
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if !NETFX_CORE
|
||||
// data sending is buffered for all protocols, so when we put data into the socket we want to send them asap
|
||||
this.connector.Client.NoDelay = true;
|
||||
#endif
|
||||
StartTime = DateTime.UtcNow;
|
||||
|
||||
HTTPManager.Logger.Information("HTTPConnection", "Negotiated protocol through ALPN: '" + this.connector.NegotiatedProtocol + "'", this.Context, this.CurrentRequest.Context);
|
||||
|
||||
switch (this.connector.NegotiatedProtocol)
|
||||
{
|
||||
case HTTPProtocolFactory.W3C_HTTP1:
|
||||
this.requestHandler = new Connections.HTTP1Handler(this);
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HostProtocolSupport.HTTP1));
|
||||
break;
|
||||
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
case HTTPProtocolFactory.W3C_HTTP2:
|
||||
this.requestHandler = new Connections.HTTP2.HTTP2Handler(this);
|
||||
this.CurrentRequest = null;
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HostProtocolSupport.HTTP2));
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
HTTPManager.Logger.Error("HTTPConnection", "Unknown negotiated protocol: " + this.connector.NegotiatedProtocol, this.Context, this.CurrentRequest.Context);
|
||||
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(CurrentRequest, RequestEvents.Resend));
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.requestHandler.Context.Add("Connection", this.GetHashCode());
|
||||
this.Context.Add("RequestHandler", this.requestHandler.GetHashCode());
|
||||
|
||||
this.requestHandler.RunHandler();
|
||||
LastProcessTime = DateTime.Now;
|
||||
}
|
||||
|
||||
public override void Shutdown(ShutdownTypes type)
|
||||
{
|
||||
base.Shutdown(type);
|
||||
|
||||
if (this.requestHandler != null)
|
||||
this.requestHandler.Shutdown(type);
|
||||
|
||||
switch(this.ShutdownType)
|
||||
{
|
||||
case ShutdownTypes.Immediate:
|
||||
this.connector.Dispose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
LastProcessedUri = null;
|
||||
if (this.State != HTTPConnectionStates.WaitForProtocolShutdown)
|
||||
{
|
||||
if (this.connector != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.connector.Close();
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
this.connector = null;
|
||||
}
|
||||
|
||||
if (this.requestHandler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.requestHandler.Dispose();
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
this.requestHandler = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have to connector to do not close its stream at any cost while disposing.
|
||||
// All references to this connection will be removed, so this and the connector may be finalized after some time.
|
||||
// But, finalizing (and disposing) the connector while the protocol is still active would be fatal,
|
||||
// so we have to make sure that it will not happen. This also means that the protocol has the responsibility (as always had)
|
||||
// to close the stream and TCP connection properly.
|
||||
if (this.connector != null)
|
||||
this.connector.LeaveOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
namespace BestHTTP.Connections
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible states of a Http Connection.
|
||||
/// The ideal lifecycle of a connection that has KeepAlive is the following: Initial => [Processing => WaitForRecycle => Free] => Closed.
|
||||
/// </summary>
|
||||
public enum HTTPConnectionStates
|
||||
{
|
||||
/// <summary>
|
||||
/// This Connection instance is just created.
|
||||
/// </summary>
|
||||
Initial,
|
||||
|
||||
/// <summary>
|
||||
/// This Connection is processing a request
|
||||
/// </summary>
|
||||
Processing,
|
||||
|
||||
/// <summary>
|
||||
/// Wait for the upgraded protocol to shut down.
|
||||
/// </summary>
|
||||
WaitForProtocolShutdown,
|
||||
|
||||
/// <summary>
|
||||
/// The Connection is finished processing the request, it's waiting now to deliver it's result.
|
||||
/// </summary>
|
||||
Recycle,
|
||||
|
||||
/// <summary>
|
||||
/// The request result's delivered, it's now up to processing again.
|
||||
/// </summary>
|
||||
Free,
|
||||
|
||||
/// <summary>
|
||||
/// If it's not a KeepAlive connection, or something happened, then we close this connection and remove from the pool.
|
||||
/// </summary>
|
||||
Closed,
|
||||
|
||||
/// <summary>
|
||||
/// Same as the Closed state, but processing this request requires resending the last processed request too.
|
||||
/// </summary>
|
||||
ClosedResendRequest
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace BestHTTP.Connections
|
||||
{
|
||||
public enum SupportedProtocols
|
||||
{
|
||||
Unknown,
|
||||
HTTP,
|
||||
|
||||
#if !BESTHTTP_DISABLE_WEBSOCKET
|
||||
WebSocket,
|
||||
#endif
|
||||
|
||||
#if !BESTHTTP_DISABLE_SERVERSENT_EVENTS
|
||||
ServerSentEvents
|
||||
#endif
|
||||
}
|
||||
|
||||
public static class HTTPProtocolFactory
|
||||
{
|
||||
public const string W3C_HTTP1 = "http/1.1";
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||||
public const string W3C_HTTP2 = "h2";
|
||||
#endif
|
||||
|
||||
public static HTTPResponse Get(SupportedProtocols protocol, HTTPRequest request, Stream stream, bool isStreamed, bool isFromCache)
|
||||
{
|
||||
switch (protocol)
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_WEBSOCKET && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
case SupportedProtocols.WebSocket: return new WebSocket.WebSocketResponse(request, stream, isStreamed, isFromCache);
|
||||
#endif
|
||||
default: return new HTTPResponse(request, stream, isStreamed, isFromCache);
|
||||
}
|
||||
}
|
||||
|
||||
public static SupportedProtocols GetProtocolFromUri(Uri uri)
|
||||
{
|
||||
if (uri == null || uri.Scheme == null)
|
||||
throw new Exception("Malformed URI in GetProtocolFromUri");
|
||||
|
||||
string scheme = uri.Scheme.ToLowerInvariant();
|
||||
switch (scheme)
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_WEBSOCKET
|
||||
case "ws":
|
||||
case "wss":
|
||||
return SupportedProtocols.WebSocket;
|
||||
#endif
|
||||
|
||||
default:
|
||||
return SupportedProtocols.HTTP;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsSecureProtocol(Uri uri)
|
||||
{
|
||||
if (uri == null || uri.Scheme == null)
|
||||
throw new Exception("Malformed URI in IsSecureProtocol");
|
||||
|
||||
string scheme = uri.Scheme.ToLowerInvariant();
|
||||
switch (scheme)
|
||||
{
|
||||
// http
|
||||
case "https":
|
||||
|
||||
#if !BESTHTTP_DISABLE_WEBSOCKET
|
||||
// WebSocket
|
||||
case "wss":
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,376 @@
|
|||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
#if !NETFX_CORE || UNITY_EDITOR
|
||||
using System.Net.Security;
|
||||
#endif
|
||||
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls;
|
||||
using BestHTTP.Connections.TLS;
|
||||
using System.Threading;
|
||||
#endif
|
||||
|
||||
#if NETFX_CORE
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Networking.Sockets;
|
||||
|
||||
using TcpClient = BestHTTP.PlatformSupport.TcpClient.WinRT.TcpClient;
|
||||
|
||||
//Disable CD4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
|
||||
#pragma warning disable 4014
|
||||
#else
|
||||
using TcpClient = BestHTTP.PlatformSupport.TcpClient.General.TcpClient;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
#endif
|
||||
|
||||
using BestHTTP.Timings;
|
||||
|
||||
namespace BestHTTP.Connections
|
||||
{
|
||||
public sealed class TCPConnector : IDisposable
|
||||
{
|
||||
public bool IsConnected { get { return this.Client != null && this.Client.Connected; } }
|
||||
|
||||
public string NegotiatedProtocol { get; private set; }
|
||||
|
||||
public TcpClient Client { get; private set; }
|
||||
|
||||
public Stream TopmostStream { get; private set; }
|
||||
|
||||
public Stream Stream { get; private set; }
|
||||
|
||||
public bool LeaveOpen { get; set; }
|
||||
|
||||
public void Connect(HTTPRequest request)
|
||||
{
|
||||
string negotiatedProtocol = HTTPProtocolFactory.W3C_HTTP1;
|
||||
|
||||
Uri uri =
|
||||
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
request.HasProxy ? request.Proxy.Address :
|
||||
#endif
|
||||
request.CurrentUri;
|
||||
|
||||
#region TCP Connection
|
||||
|
||||
if (Client == null)
|
||||
Client = new TcpClient();
|
||||
|
||||
if (!Client.Connected)
|
||||
{
|
||||
Client.ConnectTimeout = request.ConnectTimeout;
|
||||
|
||||
#if NETFX_CORE
|
||||
Client.UseHTTPSProtocol =
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
!Request.UseAlternateSSL &&
|
||||
#endif
|
||||
HTTPProtocolFactory.IsSecureProtocol(uri);
|
||||
#endif
|
||||
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Verbose("TCPConnector", string.Format("'{0}' - Connecting to {1}:{2}", request.CurrentUri.ToString(), uri.Host, uri.Port.ToString()), request.Context);
|
||||
|
||||
#if !NETFX_CORE && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
bool changed = false;
|
||||
int? sendBufferSize = null, receiveBufferSize = null;
|
||||
|
||||
if (HTTPManager.SendBufferSize.HasValue)
|
||||
{
|
||||
sendBufferSize = Client.SendBufferSize;
|
||||
Client.SendBufferSize = HTTPManager.SendBufferSize.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (HTTPManager.ReceiveBufferSize.HasValue)
|
||||
{
|
||||
receiveBufferSize = Client.ReceiveBufferSize;
|
||||
Client.ReceiveBufferSize = HTTPManager.ReceiveBufferSize.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
{
|
||||
if (changed)
|
||||
HTTPManager.Logger.Verbose("TCPConnector", string.Format("'{0}' - Buffer sizes changed - Send from: {1} to: {2}, Receive from: {3} to: {4}, Blocking: {5}",
|
||||
request.CurrentUri.ToString(),
|
||||
sendBufferSize,
|
||||
Client.SendBufferSize,
|
||||
receiveBufferSize,
|
||||
Client.ReceiveBufferSize,
|
||||
Client.Client.Blocking),
|
||||
request.Context);
|
||||
else
|
||||
HTTPManager.Logger.Verbose("TCPConnector", string.Format("'{0}' - Buffer sizes - Send: {1} Receive: {2} Blocking: {3}", request.CurrentUri.ToString(), Client.SendBufferSize, Client.ReceiveBufferSize, Client.Client.Blocking), request.Context);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NETFX_CORE && !UNITY_EDITOR && !ENABLE_IL2CPP
|
||||
try
|
||||
{
|
||||
Client.Connect(uri.Host, uri.Port);
|
||||
}
|
||||
finally
|
||||
{
|
||||
request.Timing.Add(TimingEventNames.TCP_Connection);
|
||||
}
|
||||
#else
|
||||
System.Net.IPAddress[] addresses = null;
|
||||
try
|
||||
{
|
||||
if (Client.ConnectTimeout > TimeSpan.Zero)
|
||||
{
|
||||
// https://forum.unity3d.com/threads/best-http-released.200006/page-37#post-3150972
|
||||
using (System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false))
|
||||
{
|
||||
IAsyncResult result = System.Net.Dns.BeginGetHostAddresses(uri.Host, (res) => { try { mre.Set(); } catch { } }, null);
|
||||
bool success = mre.WaitOne(Client.ConnectTimeout);
|
||||
if (success)
|
||||
{
|
||||
addresses = System.Net.Dns.EndGetHostAddresses(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TimeoutException("DNS resolve timed out!");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
addresses = System.Net.Dns.GetHostAddresses(uri.Host);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
request.Timing.Add(TimingEventNames.DNS_Lookup);
|
||||
}
|
||||
|
||||
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
HTTPManager.Logger.Verbose("TCPConnector", string.Format("'{0}' - DNS Query returned with addresses: {1}", request.CurrentUri.ToString(), addresses != null ? addresses.Length : -1), request.Context);
|
||||
|
||||
if (request.IsCancellationRequested)
|
||||
throw new Exception("IsCancellationRequested");
|
||||
|
||||
try
|
||||
{
|
||||
Client.Connect(addresses, uri.Port, request);
|
||||
}
|
||||
finally
|
||||
{
|
||||
request.Timing.Add(TimingEventNames.TCP_Connection);
|
||||
}
|
||||
|
||||
if (request.IsCancellationRequested)
|
||||
throw new Exception("IsCancellationRequested");
|
||||
#endif
|
||||
|
||||
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
|
||||
HTTPManager.Logger.Information("TCPConnector", "Connected to " + uri.Host + ":" + uri.Port.ToString(), request.Context);
|
||||
}
|
||||
else if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
|
||||
HTTPManager.Logger.Information("TCPConnector", "Already connected to " + uri.Host + ":" + uri.Port.ToString(), request.Context);
|
||||
|
||||
#endregion
|
||||
|
||||
if (Stream == null)
|
||||
{
|
||||
bool isSecure = HTTPProtocolFactory.IsSecureProtocol(request.CurrentUri);
|
||||
|
||||
// set the stream to Client.GetStream() so the proxy, if there's any can use it directly.
|
||||
this.Stream = this.TopmostStream = Client.GetStream();
|
||||
|
||||
/*if (Stream.CanTimeout)
|
||||
Stream.ReadTimeout = Stream.WriteTimeout = (int)Request.Timeout.TotalMilliseconds;*/
|
||||
|
||||
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
if (request.HasProxy)
|
||||
{
|
||||
try
|
||||
{
|
||||
request.Proxy.Connect(this.Stream, request);
|
||||
}
|
||||
finally
|
||||
{
|
||||
request.Timing.Add(TimingEventNames.Proxy_Negotiation);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.IsCancellationRequested)
|
||||
throw new Exception("IsCancellationRequested");
|
||||
#endif
|
||||
|
||||
// proxy connect is done, we can set the stream to a buffered one. HTTPProxy requires the raw NetworkStream for HTTPResponse's ReadUnknownSize!
|
||||
this.Stream = this.TopmostStream = new BufferedReadNetworkStream(Client.GetStream(), Math.Max(8 * 1024, HTTPManager.ReceiveBufferSize ?? Client.ReceiveBufferSize));
|
||||
|
||||
// We have to use Request.CurrentUri here, because uri can be a proxy uri with a different protocol
|
||||
if (isSecure)
|
||||
{
|
||||
DateTime tlsNegotiationStartedAt = DateTime.Now;
|
||||
#region SSL Upgrade
|
||||
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
if (HTTPManager.UseAlternateSSLDefaultValue)
|
||||
{
|
||||
var handler = new TlsClientProtocol(this.Stream);
|
||||
|
||||
List<ProtocolName> protocols = new List<ProtocolName>();
|
||||
#if !BESTHTTP_DISABLE_HTTP2
|
||||
SupportedProtocols protocol = HTTPProtocolFactory.GetProtocolFromUri(request.CurrentUri);
|
||||
if (protocol == SupportedProtocols.HTTP && request.IsKeepAlive)
|
||||
{
|
||||
// http/2 over tls (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids)
|
||||
protocols.Add(ProtocolName.AsUtf8Encoding(HTTPProtocolFactory.W3C_HTTP2));
|
||||
}
|
||||
#endif
|
||||
|
||||
protocols.Add(ProtocolName.AsUtf8Encoding(HTTPProtocolFactory.W3C_HTTP1));
|
||||
|
||||
AbstractTls13Client tlsClient = null;
|
||||
if (HTTPManager.TlsClientFactory == null)
|
||||
{
|
||||
tlsClient = HTTPManager.DefaultTlsClientFactory(request, protocols);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
tlsClient = HTTPManager.TlsClientFactory(request, protocols);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception(nameof(TCPConnector), nameof(HTTPManager.TlsClientFactory), ex, request.Context);
|
||||
}
|
||||
|
||||
if (tlsClient == null)
|
||||
tlsClient = HTTPManager.DefaultTlsClientFactory(request, protocols);
|
||||
}
|
||||
|
||||
//tlsClient.LoggingContext = request.Context;
|
||||
handler.Connect(tlsClient);
|
||||
|
||||
var applicationProtocol = tlsClient.GetNegotiatedApplicationProtocol();
|
||||
if (!string.IsNullOrEmpty(applicationProtocol))
|
||||
negotiatedProtocol = applicationProtocol;
|
||||
|
||||
Stream = handler.Stream;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
#if !NETFX_CORE
|
||||
SslStream sslStream = null;
|
||||
|
||||
if (HTTPManager.ClientCertificationProvider == null)
|
||||
sslStream = new SslStream(Client.GetStream(), false, (sender, cert, chain, errors) =>
|
||||
{
|
||||
if (HTTPManager.DefaultCertificationValidator != null)
|
||||
return HTTPManager.DefaultCertificationValidator(request, cert, chain, errors);
|
||||
else
|
||||
return true;
|
||||
});
|
||||
else
|
||||
sslStream = new SslStream(Client.GetStream(), false, (sender, cert, chain, errors) =>
|
||||
{
|
||||
if (HTTPManager.DefaultCertificationValidator != null)
|
||||
return HTTPManager.DefaultCertificationValidator(request, cert, chain, errors);
|
||||
else
|
||||
return true;
|
||||
},
|
||||
(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers) =>
|
||||
{
|
||||
return HTTPManager.ClientCertificationProvider(request, targetHost, localCertificates, remoteCertificate, acceptableIssuers);
|
||||
});
|
||||
|
||||
if (!sslStream.IsAuthenticated)
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_HTTP2 && !BESTHTTP_DISABLE_ALTERNATE_SSL && false
|
||||
List<SslApplicationProtocol> protocols = new List<SslApplicationProtocol>();
|
||||
SupportedProtocols protocol = HTTPProtocolFactory.GetProtocolFromUri(request.CurrentUri);
|
||||
if (protocol == SupportedProtocols.HTTP && request.IsKeepAlive)
|
||||
{
|
||||
// http/2 over tls (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids)
|
||||
protocols.Add(new SslApplicationProtocol(HTTPProtocolFactory.W3C_HTTP2));
|
||||
}
|
||||
protocols.Add(new SslApplicationProtocol(HTTPProtocolFactory.W3C_HTTP1));
|
||||
|
||||
SslClientAuthenticationOptions options = new SslClientAuthenticationOptions();
|
||||
options.ApplicationProtocols = protocols;
|
||||
options.TargetHost = request.CurrentUri.Host;
|
||||
var task = sslStream.AuthenticateAsClientAsync(options, default(System.Threading.CancellationToken));
|
||||
task.Wait();
|
||||
|
||||
try
|
||||
{
|
||||
negotiatedProtocol = System.Text.Encoding.UTF8.GetString(sslStream.NegotiatedApplicationProtocol.Protocol.Span);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception(nameof(TCPConnector), "Accessing SslStream's NegotiatedApplicationProtocol throwed an exception, falling back using HTTP/1.1", ex, request.Context);
|
||||
negotiatedProtocol = HTTPProtocolFactory.W3C_HTTP1;
|
||||
}
|
||||
#else
|
||||
sslStream.AuthenticateAsClient(request.CurrentUri.Host);
|
||||
#endif
|
||||
}
|
||||
Stream = sslStream;
|
||||
#else
|
||||
Stream = Client.GetStream();
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
|
||||
request.Timing.Add(TimingEventNames.TLS_Negotiation, DateTime.Now - tlsNegotiationStartedAt);
|
||||
}
|
||||
}
|
||||
|
||||
this.NegotiatedProtocol = negotiatedProtocol;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (Client != null && !this.LeaveOpen)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Stream != null)
|
||||
Stream.Close();
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
Stream = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (TopmostStream != null)
|
||||
TopmostStream.Close();
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
TopmostStream = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Client.Close();
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
Client = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls;
|
||||
using BestHTTP.Logger;
|
||||
|
||||
namespace BestHTTP.Connections.TLS
|
||||
{
|
||||
public abstract class AbstractTls13Client : AbstractTlsClient, TlsAuthentication
|
||||
{
|
||||
protected static readonly int[] DefaultCipherSuites = new int[] {
|
||||
/*
|
||||
* TLS 1.3
|
||||
*/
|
||||
CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
|
||||
CipherSuite.TLS_AES_256_GCM_SHA384,
|
||||
CipherSuite.TLS_AES_128_GCM_SHA256,
|
||||
|
||||
/*
|
||||
* pre-TLS 1.3
|
||||
*/
|
||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
};
|
||||
|
||||
protected HTTPRequest _request;
|
||||
protected List<ServerName> _sniServerNames;
|
||||
protected List<ProtocolName> _protocols;
|
||||
|
||||
protected LoggingContext Context { get; private set; }
|
||||
|
||||
protected AbstractTls13Client(HTTPRequest request, List<ServerName> sniServerNames, List<ProtocolName> protocols, TlsCrypto crypto)
|
||||
: base(crypto)
|
||||
{
|
||||
this._request = request;
|
||||
|
||||
// get the request's logging context. The context has no reference to the request, so it won't keep it in memory.
|
||||
this.Context = this._request.Context;
|
||||
|
||||
this._sniServerNames = sniServerNames;
|
||||
this._protocols = protocols;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TCPConnector has to know what protocol got negotiated
|
||||
/// </summary>
|
||||
public string GetNegotiatedApplicationProtocol() => base.m_context.SecurityParameters.ApplicationProtocol?.GetUtf8Decoding();
|
||||
|
||||
// (Abstract)TLSClient facing functions
|
||||
|
||||
protected override ProtocolVersion[] GetSupportedVersions() => ProtocolVersion.TLSv13.DownTo(ProtocolVersion.TLSv12);
|
||||
protected override IList<ProtocolName> GetProtocolNames() => this._protocols;
|
||||
protected override IList<ServerName> GetSniServerNames() => this._sniServerNames;
|
||||
protected override int[] GetSupportedCipherSuites()
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(GetSupportedCipherSuites)}", this.Context);
|
||||
return TlsUtilities.GetSupportedCipherSuites(Crypto, DefaultCipherSuites);
|
||||
}
|
||||
|
||||
// TlsAuthentication implementation
|
||||
public override TlsAuthentication GetAuthentication()
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(GetAuthentication)}", this.Context);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual TlsCredentials GetClientCredentials(CertificateRequest certificateRequest)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(GetClientCredentials)}", this.Context);
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual void NotifyServerCertificate(TlsServerCertificate serverCertificate)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(NotifyServerCertificate)}", this.Context);
|
||||
}
|
||||
|
||||
public override void NotifyAlertReceived(short alertLevel, short alertDescription)
|
||||
{
|
||||
base.NotifyAlertReceived(alertLevel, alertDescription);
|
||||
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(NotifyAlertReceived)}({alertLevel}, {alertDescription})", this.Context);
|
||||
}
|
||||
|
||||
public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message, Exception cause)
|
||||
{
|
||||
base.NotifyAlertRaised(alertLevel, alertDescription, message, cause);
|
||||
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(NotifyAlertRaised)}({alertLevel}, {alertDescription}, {message}, {cause?.StackTrace})", this.Context);
|
||||
}
|
||||
|
||||
public override void NotifyHandshakeBeginning()
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(NotifyHandshakeBeginning)}", this.Context);
|
||||
}
|
||||
|
||||
public override void NotifyHandshakeComplete()
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(NotifyHandshakeComplete)}", this.Context);
|
||||
this._request = null;
|
||||
}
|
||||
|
||||
public override void NotifyNewSessionTicket(NewSessionTicket newSessionTicket)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(NotifyNewSessionTicket)}", this.Context);
|
||||
|
||||
base.NotifyNewSessionTicket(newSessionTicket);
|
||||
}
|
||||
|
||||
public override void NotifySecureRenegotiation(bool secureRenegotiation)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(NotifySecureRenegotiation)}", this.Context);
|
||||
|
||||
base.NotifySecureRenegotiation(secureRenegotiation);
|
||||
}
|
||||
|
||||
public override void NotifySelectedCipherSuite(int selectedCipherSuite)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(NotifySelectedCipherSuite)}({selectedCipherSuite})", this.Context);
|
||||
|
||||
base.NotifySelectedCipherSuite(selectedCipherSuite);
|
||||
}
|
||||
|
||||
public override void NotifySelectedPsk(TlsPsk selectedPsk)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(NotifySelectedPsk)}({selectedPsk?.PrfAlgorithm})", this.Context);
|
||||
|
||||
base.NotifySelectedPsk(selectedPsk);
|
||||
}
|
||||
|
||||
public override void NotifyServerVersion(ProtocolVersion serverVersion)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(NotifyServerVersion)}({serverVersion})", this.Context);
|
||||
|
||||
base.NotifyServerVersion(serverVersion);
|
||||
}
|
||||
|
||||
public override void NotifySessionID(byte[] sessionID)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(NotifySessionID)}", this.Context);
|
||||
|
||||
base.NotifySessionID(sessionID);
|
||||
}
|
||||
|
||||
public override void NotifySessionToResume(TlsSession session)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(NotifySessionToResume)}", this.Context);
|
||||
|
||||
base.NotifySessionToResume(session);
|
||||
}
|
||||
|
||||
public override void ProcessServerExtensions(IDictionary<int, byte[]> serverExtensions)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(AbstractTls13Client), $"{nameof(ProcessServerExtensions)}", this.Context);
|
||||
|
||||
base.ProcessServerExtensions(serverExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
using System;
|
||||
|
||||
using BestHTTP.Connections.TLS.Crypto.Impl;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Engines;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Modes;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Security;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto.Impl;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto.Impl.BC;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.IO;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto
|
||||
{
|
||||
public sealed class FastTlsCrypto : BcTlsCrypto
|
||||
{
|
||||
public FastTlsCrypto(SecureRandom entropySource)
|
||||
: base(entropySource)
|
||||
{
|
||||
}
|
||||
|
||||
public override TlsCipher CreateCipher(TlsCryptoParameters cryptoParams, int encryptionAlgorithm, int macAlgorithm)
|
||||
{
|
||||
HTTPManager.Logger.Verbose(nameof(FastTlsCrypto), $"CreateCipher({encryptionAlgorithm}, {macAlgorithm})");
|
||||
|
||||
switch (encryptionAlgorithm)
|
||||
{
|
||||
case EncryptionAlgorithm.CHACHA20_POLY1305:
|
||||
{
|
||||
// NOTE: Ignores macAlgorithm
|
||||
//return CreateChaCha20Poly1305(cryptoParams);
|
||||
FastBcChaCha20Poly1305 encrypt = new FastBcChaCha20Poly1305(true);
|
||||
FastBcChaCha20Poly1305 decrypt = new FastBcChaCha20Poly1305(false);
|
||||
|
||||
return new FastTlsAeadCipher(cryptoParams, encrypt, decrypt, 32, 16, TlsAeadCipher.AEAD_CHACHA20_POLY1305);
|
||||
}
|
||||
|
||||
case EncryptionAlgorithm.AES_128_CBC:
|
||||
case EncryptionAlgorithm.ARIA_128_CBC:
|
||||
case EncryptionAlgorithm.CAMELLIA_128_CBC:
|
||||
case EncryptionAlgorithm.SEED_CBC:
|
||||
case EncryptionAlgorithm.SM4_CBC:
|
||||
{
|
||||
//return CreateCipher_Cbc(cryptoParams, encryptionAlgorithm, 16, macAlgorithm);
|
||||
FastTlsBlockCipherImpl encrypt = new FastTlsBlockCipherImpl(CreateCbcBlockCipher(encryptionAlgorithm), true);
|
||||
FastTlsBlockCipherImpl decrypt = new FastTlsBlockCipherImpl(CreateCbcBlockCipher(encryptionAlgorithm), false);
|
||||
|
||||
TlsHmac clientMac = CreateMac(cryptoParams, macAlgorithm);
|
||||
TlsHmac serverMac = CreateMac(cryptoParams, macAlgorithm);
|
||||
|
||||
return new FastTlsBlockCipher(cryptoParams, encrypt, decrypt, clientMac, serverMac, 16);
|
||||
}
|
||||
|
||||
case EncryptionAlgorithm.AES_256_CBC:
|
||||
case EncryptionAlgorithm.ARIA_256_CBC:
|
||||
case EncryptionAlgorithm.CAMELLIA_256_CBC:
|
||||
{
|
||||
//return CreateCipher_Cbc(cryptoParams, encryptionAlgorithm, 32, macAlgorithm);
|
||||
FastTlsBlockCipherImpl encrypt = new FastTlsBlockCipherImpl(CreateCbcBlockCipher(encryptionAlgorithm), true);
|
||||
FastTlsBlockCipherImpl decrypt = new FastTlsBlockCipherImpl(CreateCbcBlockCipher(encryptionAlgorithm), false);
|
||||
|
||||
TlsHmac clientMac = CreateMac(cryptoParams, macAlgorithm);
|
||||
TlsHmac serverMac = CreateMac(cryptoParams, macAlgorithm);
|
||||
|
||||
return new FastTlsBlockCipher(cryptoParams, encrypt, decrypt, clientMac, serverMac, 32);
|
||||
}
|
||||
|
||||
case EncryptionAlgorithm.AES_128_CCM:
|
||||
{
|
||||
// NOTE: Ignores macAlgorithm
|
||||
//return CreateCipher_Aes_Ccm(cryptoParams, 16, 16);
|
||||
FastTlsAeadCipherImpl encrypt = new FastTlsAeadCipherImpl(CreateAeadCipher_Aes_Ccm(), true);
|
||||
FastTlsAeadCipherImpl decrypt = new FastTlsAeadCipherImpl(CreateAeadCipher_Aes_Ccm(), false);
|
||||
|
||||
return new FastTlsAeadCipher(cryptoParams, encrypt, decrypt, 16, 16, TlsAeadCipher.AEAD_CCM);
|
||||
|
||||
}
|
||||
case EncryptionAlgorithm.AES_128_CCM_8:
|
||||
{
|
||||
// NOTE: Ignores macAlgorithm
|
||||
//return CreateCipher_Aes_Ccm(cryptoParams, 16, 8);
|
||||
FastTlsAeadCipherImpl encrypt = new FastTlsAeadCipherImpl(CreateAeadCipher_Aes_Ccm(), true);
|
||||
FastTlsAeadCipherImpl decrypt = new FastTlsAeadCipherImpl(CreateAeadCipher_Aes_Ccm(), false);
|
||||
|
||||
return new FastTlsAeadCipher(cryptoParams, encrypt, decrypt, 16, 8, TlsAeadCipher.AEAD_CCM);
|
||||
}
|
||||
case EncryptionAlgorithm.AES_256_CCM:
|
||||
{
|
||||
// NOTE: Ignores macAlgorithm
|
||||
//return CreateCipher_Aes_Ccm(cryptoParams, 32, 16);
|
||||
FastTlsAeadCipherImpl encrypt = new FastTlsAeadCipherImpl(CreateAeadCipher_Aes_Ccm(), true);
|
||||
FastTlsAeadCipherImpl decrypt = new FastTlsAeadCipherImpl(CreateAeadCipher_Aes_Ccm(), false);
|
||||
|
||||
return new FastTlsAeadCipher(cryptoParams, encrypt, decrypt, 32, 16, TlsAeadCipher.AEAD_CCM);
|
||||
}
|
||||
case EncryptionAlgorithm.AES_256_CCM_8:
|
||||
{
|
||||
// NOTE: Ignores macAlgorithm
|
||||
//return CreateCipher_Aes_Ccm(cryptoParams, 32, 8);
|
||||
FastTlsAeadCipherImpl encrypt = new FastTlsAeadCipherImpl(CreateAeadCipher_Aes_Ccm(), true);
|
||||
FastTlsAeadCipherImpl decrypt = new FastTlsAeadCipherImpl(CreateAeadCipher_Aes_Ccm(), false);
|
||||
|
||||
return new FastTlsAeadCipher(cryptoParams, encrypt, decrypt, 32, 8, TlsAeadCipher.AEAD_CCM);
|
||||
}
|
||||
|
||||
case EncryptionAlgorithm.AES_128_GCM:
|
||||
{
|
||||
// NOTE: Ignores macAlgorithm
|
||||
//return CreateCipher_Aes_Gcm(cryptoParams, 16, 16);
|
||||
FastTlsAeadCipherImpl encrypt = new FastTlsAeadCipherImpl(CreateAeadCipher_Aes_Gcm(), true);
|
||||
FastTlsAeadCipherImpl decrypt = new FastTlsAeadCipherImpl(CreateAeadCipher_Aes_Gcm(), false);
|
||||
|
||||
return new FastTlsAeadCipher(cryptoParams, encrypt, decrypt, 16, 16, TlsAeadCipher.AEAD_GCM);
|
||||
}
|
||||
|
||||
case EncryptionAlgorithm.AES_256_GCM:
|
||||
{
|
||||
// NOTE: Ignores macAlgorithm
|
||||
//return CreateCipher_Aes_Gcm(cryptoParams, 32, 16);
|
||||
FastTlsAeadCipherImpl encrypt = new FastTlsAeadCipherImpl(CreateAeadCipher_Aes_Gcm(), true);
|
||||
FastTlsAeadCipherImpl decrypt = new FastTlsAeadCipherImpl(CreateAeadCipher_Aes_Gcm(), false);
|
||||
|
||||
return new FastTlsAeadCipher(cryptoParams, encrypt, decrypt, 32, 16, TlsAeadCipher.AEAD_GCM);
|
||||
}
|
||||
|
||||
default:
|
||||
return base.CreateCipher(cryptoParams, encryptionAlgorithm, macAlgorithm);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IBlockCipher CreateAesEngine()
|
||||
{
|
||||
//return new AesEngine();
|
||||
return new FastAesEngine();
|
||||
}
|
||||
|
||||
protected override IAeadCipher CreateCcmMode(IBlockCipher engine)
|
||||
{
|
||||
return new FastCcmBlockCipher(engine);
|
||||
}
|
||||
|
||||
protected override IAeadCipher CreateGcmMode(IBlockCipher engine)
|
||||
{
|
||||
// TODO Consider allowing custom configuration of multiplier
|
||||
return new FastGcmBlockCipher(engine);
|
||||
}
|
||||
|
||||
protected override IBlockCipher CreateCbcBlockCipher(IBlockCipher blockCipher)
|
||||
{
|
||||
return new FastCbcBlockCipher(blockCipher);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Modes.Gcm;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
|
||||
|
||||
#if BESTHTTP_WITH_BURST
|
||||
using Unity.Burst;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
#endif
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
[BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
|
||||
#if BESTHTTP_WITH_BURST
|
||||
[BurstCompile]
|
||||
#endif
|
||||
public sealed class BurstTables8kGcmMultiplier //: IGcmMultiplier
|
||||
{
|
||||
private byte[] H;
|
||||
private GcmUtilities.FieldElement[][] T;
|
||||
|
||||
public void Init(byte[] H)
|
||||
{
|
||||
if (T == null)
|
||||
{
|
||||
T = new GcmUtilities.FieldElement[2][];
|
||||
}
|
||||
else if (Arrays.AreEqual(this.H, H))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.H == null)
|
||||
this.H = Arrays.Clone(H);
|
||||
else
|
||||
{
|
||||
if (this.H.Length != H.Length)
|
||||
Array.Resize(ref this.H, H.Length);
|
||||
|
||||
Array.Copy(H, this.H, H.Length);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
if (T[i] == null)
|
||||
T[i] = new GcmUtilities.FieldElement[256];
|
||||
|
||||
GcmUtilities.FieldElement[] t = T[i];
|
||||
|
||||
// t[0] = 0
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
// t[1] = H.p^7
|
||||
GcmUtilities.AsFieldElement(this.H, out t[1]);
|
||||
GcmUtilities.MultiplyP7(ref t[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// t[1] = T[i-1][1].p^8
|
||||
GcmUtilities.MultiplyP8(ref T[i - 1][1], out t[1]);
|
||||
}
|
||||
|
||||
for (int n = 1; n < 128; ++n)
|
||||
{
|
||||
// t[2.n] = t[n].p^-1
|
||||
GcmUtilities.DivideP(ref t[n], out t[n << 1]);
|
||||
|
||||
// t[2.n + 1] = t[2.n] + t[1]
|
||||
GcmUtilities.Xor(ref t[n << 1], ref t[1], out t[(n << 1) + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void MultiplyH(byte[] x)
|
||||
{
|
||||
fixed (byte* px = x)
|
||||
fixed (GcmUtilities.FieldElement* pT0 = this.T[0])
|
||||
fixed (GcmUtilities.FieldElement* pT1 = this.T[1])
|
||||
MultiplyHImpl(px, pT0, pT1);
|
||||
}
|
||||
|
||||
#if BESTHTTP_WITH_BURST
|
||||
[BurstCompile]
|
||||
#endif
|
||||
private static unsafe void MultiplyHImpl(
|
||||
#if BESTHTTP_WITH_BURST
|
||||
[NoAlias]
|
||||
#endif
|
||||
byte* px,
|
||||
#if BESTHTTP_WITH_BURST
|
||||
[NoAlias]
|
||||
#endif
|
||||
GcmUtilities.FieldElement* pT0,
|
||||
#if BESTHTTP_WITH_BURST
|
||||
[NoAlias]
|
||||
#endif
|
||||
GcmUtilities.FieldElement* pT1)
|
||||
{
|
||||
int vPos = px[15];
|
||||
int uPos = px[14];
|
||||
ulong z1 = pT0[uPos].n1 ^ pT1[vPos].n1;
|
||||
ulong z0 = pT0[uPos].n0 ^ pT1[vPos].n0;
|
||||
|
||||
for (int i = 12; i >= 0; i -= 2)
|
||||
{
|
||||
vPos = px[i + 1];
|
||||
uPos = px[i];
|
||||
|
||||
ulong c = z1 << 48;
|
||||
z1 = pT0[uPos].n1 ^ pT1[vPos].n1 ^ ((z1 >> 16) | (z0 << 48));
|
||||
z0 = pT0[uPos].n0 ^ pT1[vPos].n0 ^ (z0 >> 16) ^ c ^ (c >> 1) ^ (c >> 2) ^ (c >> 7);
|
||||
}
|
||||
|
||||
//GcmUtilities.AsBytes(z0, z1, x);
|
||||
|
||||
//UInt32_To_BE((uint)(n >> 32), bs, off);
|
||||
uint n = (uint)(z0 >> 32);
|
||||
px[0] = (byte)(n >> 24);
|
||||
px[1] = (byte)(n >> 16);
|
||||
px[2] = (byte)(n >> 8);
|
||||
px[3] = (byte)(n);
|
||||
//UInt32_To_BE((uint)(n), bs, off + 4);
|
||||
n = (uint)(z0);
|
||||
px[4] = (byte)(n >> 24);
|
||||
px[5] = (byte)(n >> 16);
|
||||
px[6] = (byte)(n >> 8);
|
||||
px[7] = (byte)(n);
|
||||
|
||||
n = (uint)(z1 >> 32);
|
||||
px[8] = (byte)(n >> 24);
|
||||
px[9] = (byte)(n >> 16);
|
||||
px[10] = (byte)(n >> 8);
|
||||
px[11] = (byte)(n);
|
||||
//UInt32_To_BE((uint)(n), bs, off + 4);
|
||||
n = (uint)(z1);
|
||||
px[12] = (byte)(n >> 24);
|
||||
px[13] = (byte)(n >> 16);
|
||||
px[14] = (byte)(n >> 8);
|
||||
px[15] = (byte)(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,938 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
/**
|
||||
* an implementation of the AES (Rijndael), from FIPS-197.
|
||||
* <p>
|
||||
* For further details see: <a href="http://csrc.nist.gov/encryption/aes/">http://csrc.nist.gov/encryption/aes/</a>.
|
||||
*
|
||||
* This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at
|
||||
* <a href="http://fp.gladman.plus.com/cryptography_technology/rijndael/">http://fp.gladman.plus.com/cryptography_technology/rijndael/</a>
|
||||
*
|
||||
* There are three levels of tradeoff of speed vs memory
|
||||
* Because java has no preprocessor, they are written as three separate classes from which to choose
|
||||
*
|
||||
* The fastest uses 8Kbytes of static tables to precompute round calculations, 4 256 word tables for encryption
|
||||
* and 4 for decryption.
|
||||
*
|
||||
* The middle performance version uses only one 256 word table for each, for a total of 2Kbytes,
|
||||
* adding 12 rotate operations per round to compute the values contained in the other tables from
|
||||
* the contents of the first.
|
||||
*
|
||||
* The slowest version uses no static tables at all and computes the values in each round.
|
||||
* </p>
|
||||
* <p>
|
||||
* This file contains the middle performance version with 2Kbytes of static tables for round precomputation.
|
||||
* </p>
|
||||
*/
|
||||
|
||||
[BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
|
||||
public sealed class FastAesEngine
|
||||
: IBlockCipher
|
||||
{
|
||||
// The S box
|
||||
private static readonly byte[] S =
|
||||
{
|
||||
99, 124, 119, 123, 242, 107, 111, 197,
|
||||
48, 1, 103, 43, 254, 215, 171, 118,
|
||||
202, 130, 201, 125, 250, 89, 71, 240,
|
||||
173, 212, 162, 175, 156, 164, 114, 192,
|
||||
183, 253, 147, 38, 54, 63, 247, 204,
|
||||
52, 165, 229, 241, 113, 216, 49, 21,
|
||||
4, 199, 35, 195, 24, 150, 5, 154,
|
||||
7, 18, 128, 226, 235, 39, 178, 117,
|
||||
9, 131, 44, 26, 27, 110, 90, 160,
|
||||
82, 59, 214, 179, 41, 227, 47, 132,
|
||||
83, 209, 0, 237, 32, 252, 177, 91,
|
||||
106, 203, 190, 57, 74, 76, 88, 207,
|
||||
208, 239, 170, 251, 67, 77, 51, 133,
|
||||
69, 249, 2, 127, 80, 60, 159, 168,
|
||||
81, 163, 64, 143, 146, 157, 56, 245,
|
||||
188, 182, 218, 33, 16, 255, 243, 210,
|
||||
205, 12, 19, 236, 95, 151, 68, 23,
|
||||
196, 167, 126, 61, 100, 93, 25, 115,
|
||||
96, 129, 79, 220, 34, 42, 144, 136,
|
||||
70, 238, 184, 20, 222, 94, 11, 219,
|
||||
224, 50, 58, 10, 73, 6, 36, 92,
|
||||
194, 211, 172, 98, 145, 149, 228, 121,
|
||||
231, 200, 55, 109, 141, 213, 78, 169,
|
||||
108, 86, 244, 234, 101, 122, 174, 8,
|
||||
186, 120, 37, 46, 28, 166, 180, 198,
|
||||
232, 221, 116, 31, 75, 189, 139, 138,
|
||||
112, 62, 181, 102, 72, 3, 246, 14,
|
||||
97, 53, 87, 185, 134, 193, 29, 158,
|
||||
225, 248, 152, 17, 105, 217, 142, 148,
|
||||
155, 30, 135, 233, 206, 85, 40, 223,
|
||||
140, 161, 137, 13, 191, 230, 66, 104,
|
||||
65, 153, 45, 15, 176, 84, 187, 22,
|
||||
};
|
||||
|
||||
// The inverse S-box
|
||||
private static readonly byte[] Si =
|
||||
{
|
||||
82, 9, 106, 213, 48, 54, 165, 56,
|
||||
191, 64, 163, 158, 129, 243, 215, 251,
|
||||
124, 227, 57, 130, 155, 47, 255, 135,
|
||||
52, 142, 67, 68, 196, 222, 233, 203,
|
||||
84, 123, 148, 50, 166, 194, 35, 61,
|
||||
238, 76, 149, 11, 66, 250, 195, 78,
|
||||
8, 46, 161, 102, 40, 217, 36, 178,
|
||||
118, 91, 162, 73, 109, 139, 209, 37,
|
||||
114, 248, 246, 100, 134, 104, 152, 22,
|
||||
212, 164, 92, 204, 93, 101, 182, 146,
|
||||
108, 112, 72, 80, 253, 237, 185, 218,
|
||||
94, 21, 70, 87, 167, 141, 157, 132,
|
||||
144, 216, 171, 0, 140, 188, 211, 10,
|
||||
247, 228, 88, 5, 184, 179, 69, 6,
|
||||
208, 44, 30, 143, 202, 63, 15, 2,
|
||||
193, 175, 189, 3, 1, 19, 138, 107,
|
||||
58, 145, 17, 65, 79, 103, 220, 234,
|
||||
151, 242, 207, 206, 240, 180, 230, 115,
|
||||
150, 172, 116, 34, 231, 173, 53, 133,
|
||||
226, 249, 55, 232, 28, 117, 223, 110,
|
||||
71, 241, 26, 113, 29, 41, 197, 137,
|
||||
111, 183, 98, 14, 170, 24, 190, 27,
|
||||
252, 86, 62, 75, 198, 210, 121, 32,
|
||||
154, 219, 192, 254, 120, 205, 90, 244,
|
||||
31, 221, 168, 51, 136, 7, 199, 49,
|
||||
177, 18, 16, 89, 39, 128, 236, 95,
|
||||
96, 81, 127, 169, 25, 181, 74, 13,
|
||||
45, 229, 122, 159, 147, 201, 156, 239,
|
||||
160, 224, 59, 77, 174, 42, 245, 176,
|
||||
200, 235, 187, 60, 131, 83, 153, 97,
|
||||
23, 43, 4, 126, 186, 119, 214, 38,
|
||||
225, 105, 20, 99, 85, 33, 12, 125,
|
||||
};
|
||||
|
||||
// vector used in calculating key schedule (powers of x in GF(256))
|
||||
private static readonly byte[] rcon =
|
||||
{
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
|
||||
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91
|
||||
};
|
||||
|
||||
// precomputation tables of calculations for rounds
|
||||
private static readonly uint[] T0 =
|
||||
{
|
||||
0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff,
|
||||
0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102,
|
||||
0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d,
|
||||
0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa,
|
||||
0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, 0xecadad41,
|
||||
0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453,
|
||||
0x967272e4, 0x5bc0c09b, 0xc2b7b775, 0x1cfdfde1, 0xae93933d,
|
||||
0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83,
|
||||
0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x08f1f1f9, 0x937171e2,
|
||||
0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795,
|
||||
0x65232346, 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a,
|
||||
0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df,
|
||||
0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912,
|
||||
0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, 0xb26e6edc,
|
||||
0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7,
|
||||
0xceb3b37d, 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413,
|
||||
0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040,
|
||||
0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d,
|
||||
0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0,
|
||||
0x4acfcf85, 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed,
|
||||
0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a,
|
||||
0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78,
|
||||
0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, 0xc0404080,
|
||||
0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1,
|
||||
0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, 0x30101020,
|
||||
0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18,
|
||||
0x35131326, 0x2fececc3, 0xe15f5fbe, 0xa2979735, 0xcc444488,
|
||||
0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a,
|
||||
0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0,
|
||||
0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54,
|
||||
0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b,
|
||||
0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad,
|
||||
0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992,
|
||||
0x0a06060c, 0x6c242448, 0xe45c5cb8, 0x5dc2c29f, 0x6ed3d3bd,
|
||||
0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3,
|
||||
0x8b7979f2, 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda,
|
||||
0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8,
|
||||
0xfa5656ac, 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4,
|
||||
0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a,
|
||||
0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697,
|
||||
0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, 0xdd4b4b96,
|
||||
0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c,
|
||||
0xc4b5b571, 0xaa6666cc, 0xd8484890, 0x05030306, 0x01f6f6f7,
|
||||
0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969,
|
||||
0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9,
|
||||
0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9,
|
||||
0x898e8e07, 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715,
|
||||
0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5,
|
||||
0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65,
|
||||
0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, 0xb0999929,
|
||||
0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d,
|
||||
0x3a16162c
|
||||
};
|
||||
|
||||
private static readonly uint[] Tinv0 =
|
||||
{
|
||||
0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b,
|
||||
0xf1459d1f, 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad,
|
||||
0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526,
|
||||
0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d,
|
||||
0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, 0xe75f8f03,
|
||||
0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458,
|
||||
0x2969e049, 0x44c8c98e, 0x6a89c275, 0x78798ef4, 0x6b3e5899,
|
||||
0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d,
|
||||
0x184adf63, 0x82311ae5, 0x60335197, 0x457f5362, 0xe07764b1,
|
||||
0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f,
|
||||
0x876cde94, 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3,
|
||||
0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3,
|
||||
0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a,
|
||||
0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, 0xd5be0506,
|
||||
0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05,
|
||||
0x75ebf6a4, 0x39ec830b, 0xaaef6040, 0x069f715e, 0x51106ebd,
|
||||
0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491,
|
||||
0x055dc471, 0x6fd40604, 0xff155060, 0x24fb9819, 0x97e9bdd6,
|
||||
0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7,
|
||||
0xdbeec879, 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000,
|
||||
0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd,
|
||||
0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68,
|
||||
0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, 0xd296eeb4,
|
||||
0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c,
|
||||
0x0aba93e2, 0xe52aa0c0, 0x43e0223c, 0x1d171b12, 0x0b0d090e,
|
||||
0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af,
|
||||
0xbbdd99ee, 0xfd607fa3, 0x9f2601f7, 0xbcf5725c, 0xc53b6644,
|
||||
0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8,
|
||||
0xcadc31d7, 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85,
|
||||
0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc,
|
||||
0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411,
|
||||
0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, 0xef903322,
|
||||
0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6,
|
||||
0x28de7aa5, 0x268eb7da, 0xa4bfad3f, 0xe49d3a2c, 0x0d927850,
|
||||
0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e,
|
||||
0xf5afc382, 0xbe805d9f, 0x7c93d069, 0xa92dd56f, 0xb31225cf,
|
||||
0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd,
|
||||
0xf418596e, 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa,
|
||||
0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea,
|
||||
0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235,
|
||||
0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, 0x4a9804f1,
|
||||
0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43,
|
||||
0x544daacc, 0xdf0496e4, 0xe3b5d19e, 0x1b886a4c, 0xb81f2cc1,
|
||||
0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb,
|
||||
0x5a1d67b3, 0x52d2db92, 0x335610e9, 0x1347d66d, 0x8c61d79a,
|
||||
0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7,
|
||||
0xede51ce1, 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418,
|
||||
0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478,
|
||||
0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16,
|
||||
0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, 0xdeb30c08,
|
||||
0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48,
|
||||
0x4257b8d0
|
||||
};
|
||||
|
||||
private static uint Shift(uint r, int shift)
|
||||
{
|
||||
return (r >> shift) | (r << (32 - shift));
|
||||
}
|
||||
|
||||
/* multiply four bytes in GF(2^8) by 'x' {02} in parallel */
|
||||
|
||||
private const uint m1 = 0x80808080;
|
||||
private const uint m2 = 0x7f7f7f7f;
|
||||
private const uint m3 = 0x0000001b;
|
||||
private const uint m4 = 0xC0C0C0C0;
|
||||
private const uint m5 = 0x3f3f3f3f;
|
||||
|
||||
private static uint FFmulX(uint x)
|
||||
{
|
||||
return ((x & m2) << 1) ^ (((x & m1) >> 7) * m3);
|
||||
}
|
||||
|
||||
private static uint FFmulX2(uint x)
|
||||
{
|
||||
uint t0 = (x & m5) << 2;
|
||||
uint t1 = (x & m4);
|
||||
t1 ^= (t1 >> 1);
|
||||
return t0 ^ (t1 >> 2) ^ (t1 >> 5);
|
||||
}
|
||||
|
||||
/*
|
||||
The following defines provide alternative definitions of FFmulX that might
|
||||
give improved performance if a fast 32-bit multiply is not available.
|
||||
|
||||
private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & m2) << 1) ^ ((u >>> 3) | (u >>> 6)); }
|
||||
private static final int m4 = 0x1b1b1b1b;
|
||||
private int FFmulX(int x) { int u = x & m1; return ((x & m2) << 1) ^ ((u - (u >>> 7)) & m4); }
|
||||
|
||||
*/
|
||||
|
||||
private static uint Inv_Mcol(uint x)
|
||||
{
|
||||
uint t0, t1;
|
||||
t0 = x;
|
||||
t1 = t0 ^ Shift(t0, 8);
|
||||
t0 ^= FFmulX(t1);
|
||||
t1 ^= FFmulX2(t0);
|
||||
t0 ^= t1 ^ Shift(t1, 16);
|
||||
return t0;
|
||||
}
|
||||
|
||||
private static uint SubWord(uint x)
|
||||
{
|
||||
return (uint)S[x & 255]
|
||||
| (((uint)S[(x >> 8) & 255]) << 8)
|
||||
| (((uint)S[(x >> 16) & 255]) << 16)
|
||||
| (((uint)S[(x >> 24) & 255]) << 24);
|
||||
}
|
||||
|
||||
uint[][] W = null;
|
||||
|
||||
/**
|
||||
* Calculate the necessary round keys
|
||||
* The number of calculations depends on key size and block size
|
||||
* AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits
|
||||
* This code is written assuming those are the only possible values
|
||||
*/
|
||||
private uint[][] GenerateWorkingKey(byte[] key, bool forEncryption)
|
||||
{
|
||||
int keyLen = key.Length;
|
||||
if (keyLen < 16 || keyLen > 32 || (keyLen & 7) != 0)
|
||||
throw new ArgumentException("Key length not 128/192/256 bits.");
|
||||
|
||||
int KC = keyLen >> 2;
|
||||
this.ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes
|
||||
|
||||
if (W == null || W.Length < ROUNDS + 1)
|
||||
{
|
||||
W = new uint[ROUNDS + 1][]; // 4 words in a block
|
||||
for (int i = 0; i <= ROUNDS; ++i)
|
||||
{
|
||||
W[i] = new uint[4];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < W.Length; ++i)
|
||||
Array.Clear(W[i], 0, W[i].Length);
|
||||
}
|
||||
|
||||
switch (KC)
|
||||
{
|
||||
case 4:
|
||||
{
|
||||
uint t0 = Pack.LE_To_UInt32(key, 0); W[0][0] = t0;
|
||||
uint t1 = Pack.LE_To_UInt32(key, 4); W[0][1] = t1;
|
||||
uint t2 = Pack.LE_To_UInt32(key, 8); W[0][2] = t2;
|
||||
uint t3 = Pack.LE_To_UInt32(key, 12); W[0][3] = t3;
|
||||
|
||||
for (int i = 1; i <= 10; ++i)
|
||||
{
|
||||
uint u = SubWord(Shift(t3, 8)) ^ rcon[i - 1];
|
||||
t0 ^= u; W[i][0] = t0;
|
||||
t1 ^= t0; W[i][1] = t1;
|
||||
t2 ^= t1; W[i][2] = t2;
|
||||
t3 ^= t2; W[i][3] = t3;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
uint t0 = Pack.LE_To_UInt32(key, 0); W[0][0] = t0;
|
||||
uint t1 = Pack.LE_To_UInt32(key, 4); W[0][1] = t1;
|
||||
uint t2 = Pack.LE_To_UInt32(key, 8); W[0][2] = t2;
|
||||
uint t3 = Pack.LE_To_UInt32(key, 12); W[0][3] = t3;
|
||||
uint t4 = Pack.LE_To_UInt32(key, 16); W[1][0] = t4;
|
||||
uint t5 = Pack.LE_To_UInt32(key, 20); W[1][1] = t5;
|
||||
|
||||
uint rcon = 1;
|
||||
uint u = SubWord(Shift(t5, 8)) ^ rcon; rcon <<= 1;
|
||||
t0 ^= u; W[1][2] = t0;
|
||||
t1 ^= t0; W[1][3] = t1;
|
||||
t2 ^= t1; W[2][0] = t2;
|
||||
t3 ^= t2; W[2][1] = t3;
|
||||
t4 ^= t3; W[2][2] = t4;
|
||||
t5 ^= t4; W[2][3] = t5;
|
||||
|
||||
for (int i = 3; i < 12; i += 3)
|
||||
{
|
||||
u = SubWord(Shift(t5, 8)) ^ rcon; rcon <<= 1;
|
||||
t0 ^= u; W[i][0] = t0;
|
||||
t1 ^= t0; W[i][1] = t1;
|
||||
t2 ^= t1; W[i][2] = t2;
|
||||
t3 ^= t2; W[i][3] = t3;
|
||||
t4 ^= t3; W[i + 1][0] = t4;
|
||||
t5 ^= t4; W[i + 1][1] = t5;
|
||||
u = SubWord(Shift(t5, 8)) ^ rcon; rcon <<= 1;
|
||||
t0 ^= u; W[i + 1][2] = t0;
|
||||
t1 ^= t0; W[i + 1][3] = t1;
|
||||
t2 ^= t1; W[i + 2][0] = t2;
|
||||
t3 ^= t2; W[i + 2][1] = t3;
|
||||
t4 ^= t3; W[i + 2][2] = t4;
|
||||
t5 ^= t4; W[i + 2][3] = t5;
|
||||
}
|
||||
|
||||
u = SubWord(Shift(t5, 8)) ^ rcon;
|
||||
t0 ^= u; W[12][0] = t0;
|
||||
t1 ^= t0; W[12][1] = t1;
|
||||
t2 ^= t1; W[12][2] = t2;
|
||||
t3 ^= t2; W[12][3] = t3;
|
||||
|
||||
break;
|
||||
}
|
||||
case 8:
|
||||
{
|
||||
uint t0 = Pack.LE_To_UInt32(key, 0); W[0][0] = t0;
|
||||
uint t1 = Pack.LE_To_UInt32(key, 4); W[0][1] = t1;
|
||||
uint t2 = Pack.LE_To_UInt32(key, 8); W[0][2] = t2;
|
||||
uint t3 = Pack.LE_To_UInt32(key, 12); W[0][3] = t3;
|
||||
uint t4 = Pack.LE_To_UInt32(key, 16); W[1][0] = t4;
|
||||
uint t5 = Pack.LE_To_UInt32(key, 20); W[1][1] = t5;
|
||||
uint t6 = Pack.LE_To_UInt32(key, 24); W[1][2] = t6;
|
||||
uint t7 = Pack.LE_To_UInt32(key, 28); W[1][3] = t7;
|
||||
|
||||
uint u, rcon = 1;
|
||||
|
||||
for (int i = 2; i < 14; i += 2)
|
||||
{
|
||||
u = SubWord(Shift(t7, 8)) ^ rcon; rcon <<= 1;
|
||||
t0 ^= u; W[i][0] = t0;
|
||||
t1 ^= t0; W[i][1] = t1;
|
||||
t2 ^= t1; W[i][2] = t2;
|
||||
t3 ^= t2; W[i][3] = t3;
|
||||
u = SubWord(t3);
|
||||
t4 ^= u; W[i + 1][0] = t4;
|
||||
t5 ^= t4; W[i + 1][1] = t5;
|
||||
t6 ^= t5; W[i + 1][2] = t6;
|
||||
t7 ^= t6; W[i + 1][3] = t7;
|
||||
}
|
||||
|
||||
u = SubWord(Shift(t7, 8)) ^ rcon;
|
||||
t0 ^= u; W[14][0] = t0;
|
||||
t1 ^= t0; W[14][1] = t1;
|
||||
t2 ^= t1; W[14][2] = t2;
|
||||
t3 ^= t2; W[14][3] = t3;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new InvalidOperationException("Should never get here");
|
||||
}
|
||||
}
|
||||
|
||||
if (!forEncryption)
|
||||
{
|
||||
for (int j = 1; j < ROUNDS; j++)
|
||||
{
|
||||
uint[] w = W[j];
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
w[i] = Inv_Mcol(w[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return W;
|
||||
}
|
||||
|
||||
private int ROUNDS;
|
||||
private uint[][] WorkingKey;
|
||||
private bool forEncryption;
|
||||
|
||||
private byte[] s;
|
||||
|
||||
private const int BLOCK_SIZE = 16;
|
||||
|
||||
/**
|
||||
* default constructor - 128 bit block size.
|
||||
*/
|
||||
public FastAesEngine()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* initialise an AES cipher.
|
||||
*
|
||||
* @param forEncryption whether or not we are for encryption.
|
||||
* @param parameters the parameters required to set up the cipher.
|
||||
* @exception ArgumentException if the parameters argument is
|
||||
* inappropriate.
|
||||
*/
|
||||
public void Init(bool forEncryption, ICipherParameters parameters)
|
||||
{
|
||||
if (!(parameters is KeyParameter keyParameter))
|
||||
throw new ArgumentException("invalid parameter passed to AES init - "
|
||||
+ BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.GetTypeName(parameters));
|
||||
|
||||
WorkingKey = GenerateWorkingKey(keyParameter.GetKey(), forEncryption);
|
||||
|
||||
this.forEncryption = forEncryption;
|
||||
this.s = /*Arrays.Clone*/(forEncryption ? S : Si);
|
||||
}
|
||||
|
||||
public string AlgorithmName
|
||||
{
|
||||
get { return "AES"; }
|
||||
}
|
||||
|
||||
public int GetBlockSize()
|
||||
{
|
||||
return BLOCK_SIZE;
|
||||
}
|
||||
|
||||
public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
|
||||
{
|
||||
if (WorkingKey == null)
|
||||
throw new InvalidOperationException("AES engine not initialised");
|
||||
|
||||
Check.DataLength(input, inOff, 16, "input buffer too short");
|
||||
Check.OutputLength(output, outOff, 16, "output buffer too short");
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
if (forEncryption)
|
||||
{
|
||||
EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff), WorkingKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff), WorkingKey);
|
||||
}
|
||||
#else
|
||||
if (forEncryption)
|
||||
{
|
||||
EncryptBlock(input, inOff, output, outOff, WorkingKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
DecryptBlock(input, inOff, output, outOff, WorkingKey);
|
||||
}
|
||||
#endif
|
||||
|
||||
return BLOCK_SIZE;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public unsafe int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
|
||||
{
|
||||
if (WorkingKey == null)
|
||||
throw new InvalidOperationException("AES engine not initialised");
|
||||
|
||||
Check.DataLength(input, 16, "input buffer too short");
|
||||
Check.OutputLength(output, 16, "output buffer too short");
|
||||
|
||||
if (forEncryption)
|
||||
{
|
||||
//EncryptBlock(input, output, WorkingKey);
|
||||
|
||||
uint C0 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(input);
|
||||
uint C1 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(input[4..]);
|
||||
uint C2 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(input[8..]);
|
||||
uint C3 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(input[12..]);
|
||||
|
||||
uint[] kw = WorkingKey[0];
|
||||
uint t0 = C0 ^ kw[0];
|
||||
uint t1 = C1 ^ kw[1];
|
||||
uint t2 = C2 ^ kw[2];
|
||||
|
||||
uint r0, r1, r2, r3 = C3 ^ kw[3];
|
||||
int r = 1;
|
||||
uint tmp1, tmp2, tmp3;
|
||||
uint shift1, shift2, shift3;
|
||||
|
||||
fixed (uint* pT0 = T0)
|
||||
{
|
||||
while (r < ROUNDS - 1)
|
||||
{
|
||||
kw = WorkingKey[r++];
|
||||
fixed (uint* pkw = kw)
|
||||
{
|
||||
tmp1 = pT0[(t1 >> 8) & 255]; tmp2 = pT0[(t2 >> 16) & 255]; tmp3 = pT0[(r3 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r0 = pT0[t0 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[0];
|
||||
|
||||
tmp1 = pT0[(t2 >> 8) & 255]; tmp2 = pT0[(r3 >> 16) & 255]; tmp3 = pT0[(t0 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r1 = pT0[t1 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[1];
|
||||
|
||||
tmp1 = pT0[(r3 >> 8) & 255]; tmp2 = pT0[(t0 >> 16) & 255]; tmp3 = pT0[(t1 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r2 = pT0[t2 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[2];
|
||||
|
||||
tmp1 = pT0[(t0 >> 8) & 255]; tmp2 = pT0[(t1 >> 16) & 255]; tmp3 = pT0[(t2 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r3 = pT0[r3 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[3];
|
||||
}
|
||||
|
||||
kw = WorkingKey[r++];
|
||||
|
||||
fixed (uint* pkw = kw)
|
||||
{
|
||||
tmp1 = pT0[(r1 >> 8) & 255]; tmp2 = pT0[(r2 >> 16) & 255]; tmp3 = pT0[(r3 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
t0 = pT0[r0 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[0];
|
||||
|
||||
tmp1 = pT0[(r2 >> 8) & 255]; tmp2 = pT0[(r3 >> 16) & 255]; tmp3 = pT0[(r0 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
t1 = pT0[r1 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[1];
|
||||
|
||||
tmp1 = pT0[(r3 >> 8) & 255]; tmp2 = pT0[(r0 >> 16) & 255]; tmp3 = pT0[(r1 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
t2 = pT0[r2 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[2];
|
||||
|
||||
tmp1 = pT0[(r0 >> 8) & 255]; tmp2 = pT0[(r1 >> 16) & 255]; tmp3 = pT0[(r2 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r3 = pT0[r3 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[3];
|
||||
}
|
||||
}
|
||||
|
||||
kw = WorkingKey[r++];
|
||||
fixed (uint* pkw = kw)
|
||||
{
|
||||
tmp1 = pT0[(t1 >> 8) & 255]; tmp2 = pT0[(t2 >> 16) & 255]; tmp3 = pT0[(r3 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r0 = pT0[t0 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[0];
|
||||
|
||||
tmp1 = pT0[(t2 >> 8) & 255]; tmp2 = pT0[(r3 >> 16) & 255]; tmp3 = pT0[(t0 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r1 = pT0[t1 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[1];
|
||||
|
||||
tmp1 = pT0[(r3 >> 8) & 255]; tmp2 = pT0[(t0 >> 16) & 255]; tmp3 = pT0[(t1 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r2 = pT0[t2 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[2];
|
||||
|
||||
tmp1 = pT0[(t0 >> 8) & 255]; tmp2 = pT0[(t1 >> 16) & 255]; tmp3 = pT0[(t2 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r3 = pT0[r3 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[3];
|
||||
}
|
||||
}
|
||||
|
||||
// the final round's table is a simple function of S so we don't use a whole other four tables for it
|
||||
|
||||
kw = WorkingKey[r];
|
||||
fixed (uint* pkw = kw)
|
||||
fixed (byte* pS = S)
|
||||
fixed (byte* ps = s)
|
||||
{
|
||||
C0 = (uint)pS[r0 & 255] ^ (((uint)pS[(r1 >> 8) & 255]) << 8) ^ (((uint)ps[(r2 >> 16) & 255]) << 16) ^ (((uint)ps[(r3 >> 24) & 255]) << 24) ^ pkw[0];
|
||||
C1 = (uint)ps[r1 & 255] ^ (((uint)pS[(r2 >> 8) & 255]) << 8) ^ (((uint)pS[(r3 >> 16) & 255]) << 16) ^ (((uint)ps[(r0 >> 24) & 255]) << 24) ^ pkw[1];
|
||||
C2 = (uint)ps[r2 & 255] ^ (((uint)pS[(r3 >> 8) & 255]) << 8) ^ (((uint)pS[(r0 >> 16) & 255]) << 16) ^ (((uint)pS[(r1 >> 24) & 255]) << 24) ^ pkw[2];
|
||||
C3 = (uint)ps[r3 & 255] ^ (((uint)ps[(r0 >> 8) & 255]) << 8) ^ (((uint)ps[(r1 >> 16) & 255]) << 16) ^ (((uint)pS[(r2 >> 24) & 255]) << 24) ^ pkw[3];
|
||||
}
|
||||
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(output, C0);
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(output[4..], C1);
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(output[8..], C2);
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(output[12..], C3);
|
||||
}
|
||||
else
|
||||
{
|
||||
//DecryptBlock(input, output, WorkingKey);
|
||||
|
||||
uint C0 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(input);
|
||||
uint C1 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(input[4..]);
|
||||
uint C2 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(input[8..]);
|
||||
uint C3 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(input[12..]);
|
||||
|
||||
uint[] kw = WorkingKey[ROUNDS];
|
||||
uint t0 = C0 ^ kw[0];
|
||||
uint t1 = C1 ^ kw[1];
|
||||
uint t2 = C2 ^ kw[2];
|
||||
|
||||
uint r0, r1, r2, r3 = C3 ^ kw[3];
|
||||
int r = ROUNDS - 1;
|
||||
|
||||
uint tmp1, tmp2, tmp3;
|
||||
uint shift1, shift2, shift3;
|
||||
|
||||
fixed (uint* pTinv0 = Tinv0)
|
||||
{
|
||||
while (r > 1)
|
||||
{
|
||||
kw = WorkingKey[r--];
|
||||
fixed (uint* pkw = kw)
|
||||
{
|
||||
tmp1 = pTinv0[(r3 >> 8) & 255]; tmp2 = pTinv0[(t2 >> 16) & 255]; tmp3 = pTinv0[(t1 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r0 = pTinv0[t0 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[0];
|
||||
|
||||
tmp1 = pTinv0[(t0 >> 8) & 255]; tmp2 = pTinv0[(r3 >> 16) & 255]; tmp3 = pTinv0[(t2 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r1 = pTinv0[t1 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[1];
|
||||
|
||||
tmp1 = pTinv0[(t1 >> 8) & 255]; tmp2 = pTinv0[(t0 >> 16) & 255]; tmp3 = pTinv0[(r3 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r2 = pTinv0[t2 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[2];
|
||||
|
||||
tmp1 = pTinv0[(t2 >> 8) & 255]; tmp2 = pTinv0[(t1 >> 16) & 255]; tmp3 = pTinv0[(t0 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r3 = pTinv0[r3 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[3];
|
||||
}
|
||||
|
||||
kw = WorkingKey[r--];
|
||||
|
||||
fixed (uint* pkw = kw)
|
||||
{
|
||||
tmp1 = pTinv0[(r3 >> 8) & 255]; tmp2 = pTinv0[(r2 >> 16) & 255]; tmp3 = pTinv0[(r1 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
t0 = pTinv0[r0 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[0];
|
||||
|
||||
tmp1 = pTinv0[(r0 >> 8) & 255]; tmp2 = pTinv0[(r3 >> 16) & 255]; tmp3 = pTinv0[(r2 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
t1 = pTinv0[r1 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[1];
|
||||
|
||||
tmp1 = pTinv0[(r1 >> 8) & 255]; tmp2 = pTinv0[(r0 >> 16) & 255]; tmp3 = pTinv0[(r3 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
t2 = pTinv0[r2 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[2];
|
||||
|
||||
tmp1 = pTinv0[(r2 >> 8) & 255]; tmp2 = pTinv0[(r1 >> 16) & 255]; tmp3 = pTinv0[(r0 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r3 = pTinv0[r3 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[3];
|
||||
}
|
||||
}
|
||||
|
||||
kw = WorkingKey[1];
|
||||
|
||||
fixed (uint* pkw = kw)
|
||||
{
|
||||
tmp1 = pTinv0[(r3 >> 8) & 255]; tmp2 = pTinv0[(t2 >> 16) & 255]; tmp3 = pTinv0[(t1 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r0 = pTinv0[t0 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[0];
|
||||
|
||||
tmp1 = pTinv0[(t0 >> 8) & 255]; tmp2 = pTinv0[(r3 >> 16) & 255]; tmp3 = pTinv0[(t2 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r1 = pTinv0[t1 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[1];
|
||||
|
||||
tmp1 = pTinv0[(t1 >> 8) & 255]; tmp2 = pTinv0[(t0 >> 16) & 255]; tmp3 = pTinv0[(r3 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r2 = pTinv0[t2 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[2];
|
||||
|
||||
tmp1 = pTinv0[(t2 >> 8) & 255]; tmp2 = pTinv0[(t1 >> 16) & 255]; tmp3 = pTinv0[(t0 >> 24) & 255];
|
||||
shift1 = (tmp1 >> 24) | (tmp1 << 8); shift2 = (tmp2 >> 16) | (tmp2 << 16); shift3 = (tmp3 >> 8) | (tmp3 << 24);
|
||||
r3 = pTinv0[r3 & 255] ^ shift1 ^ shift2 ^ shift3 ^ pkw[3];
|
||||
}
|
||||
}
|
||||
// the final round's table is a simple function of Si so we don't use a whole other four tables for it
|
||||
|
||||
kw = WorkingKey[0];
|
||||
fixed (uint* pkw = kw)
|
||||
fixed(byte* pSi = Si)
|
||||
fixed (byte* ps = s)
|
||||
{
|
||||
C0 = (uint)pSi[r0 & 255] ^ (((uint)ps[(r3 >> 8) & 255]) << 8) ^ (((uint)ps[(r2 >> 16) & 255]) << 16) ^ (((uint)pSi[(r1 >> 24) & 255]) << 24) ^ pkw[0];
|
||||
C1 = (uint)ps[r1 & 255] ^ (((uint)ps[(r0 >> 8) & 255]) << 8) ^ (((uint)pSi[(r3 >> 16) & 255]) << 16) ^ (((uint)ps[(r2 >> 24) & 255]) << 24) ^ pkw[1];
|
||||
C2 = (uint)ps[r2 & 255] ^ (((uint)pSi[(r1 >> 8) & 255]) << 8) ^ (((uint)pSi[(r0 >> 16) & 255]) << 16) ^ (((uint)ps[(r3 >> 24) & 255]) << 24) ^ pkw[2];
|
||||
C3 = (uint)pSi[r3 & 255] ^ (((uint)ps[(r2 >> 8) & 255]) << 8) ^ (((uint)ps[(r1 >> 16) & 255]) << 16) ^ (((uint)ps[(r0 >> 24) & 255]) << 24) ^ pkw[3];
|
||||
}
|
||||
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(output, C0);
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(output[4..], C1);
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(output[8..], C2);
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(output[12..], C3);
|
||||
}
|
||||
|
||||
return BLOCK_SIZE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
private void EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output, uint[][] KW)
|
||||
{
|
||||
uint C0 = Pack.LE_To_UInt32(input);
|
||||
uint C1 = Pack.LE_To_UInt32(input[4..]);
|
||||
uint C2 = Pack.LE_To_UInt32(input[8..]);
|
||||
uint C3 = Pack.LE_To_UInt32(input[12..]);
|
||||
|
||||
uint[] kw = KW[0];
|
||||
uint t0 = C0 ^ kw[0];
|
||||
uint t1 = C1 ^ kw[1];
|
||||
uint t2 = C2 ^ kw[2];
|
||||
|
||||
uint r0, r1, r2, r3 = C3 ^ kw[3];
|
||||
int r = 1;
|
||||
while (r < ROUNDS - 1)
|
||||
{
|
||||
kw = KW[r++];
|
||||
r0 = T0[t0 & 255] ^ Shift(T0[(t1 >> 8) & 255], 24) ^ Shift(T0[(t2 >> 16) & 255], 16) ^ Shift(T0[(r3 >> 24) & 255], 8) ^ kw[0];
|
||||
r1 = T0[t1 & 255] ^ Shift(T0[(t2 >> 8) & 255], 24) ^ Shift(T0[(r3 >> 16) & 255], 16) ^ Shift(T0[(t0 >> 24) & 255], 8) ^ kw[1];
|
||||
r2 = T0[t2 & 255] ^ Shift(T0[(r3 >> 8) & 255], 24) ^ Shift(T0[(t0 >> 16) & 255], 16) ^ Shift(T0[(t1 >> 24) & 255], 8) ^ kw[2];
|
||||
r3 = T0[r3 & 255] ^ Shift(T0[(t0 >> 8) & 255], 24) ^ Shift(T0[(t1 >> 16) & 255], 16) ^ Shift(T0[(t2 >> 24) & 255], 8) ^ kw[3];
|
||||
kw = KW[r++];
|
||||
t0 = T0[r0 & 255] ^ Shift(T0[(r1 >> 8) & 255], 24) ^ Shift(T0[(r2 >> 16) & 255], 16) ^ Shift(T0[(r3 >> 24) & 255], 8) ^ kw[0];
|
||||
t1 = T0[r1 & 255] ^ Shift(T0[(r2 >> 8) & 255], 24) ^ Shift(T0[(r3 >> 16) & 255], 16) ^ Shift(T0[(r0 >> 24) & 255], 8) ^ kw[1];
|
||||
t2 = T0[r2 & 255] ^ Shift(T0[(r3 >> 8) & 255], 24) ^ Shift(T0[(r0 >> 16) & 255], 16) ^ Shift(T0[(r1 >> 24) & 255], 8) ^ kw[2];
|
||||
r3 = T0[r3 & 255] ^ Shift(T0[(r0 >> 8) & 255], 24) ^ Shift(T0[(r1 >> 16) & 255], 16) ^ Shift(T0[(r2 >> 24) & 255], 8) ^ kw[3];
|
||||
}
|
||||
|
||||
kw = KW[r++];
|
||||
r0 = T0[t0 & 255] ^ Shift(T0[(t1 >> 8) & 255], 24) ^ Shift(T0[(t2 >> 16) & 255], 16) ^ Shift(T0[(r3 >> 24) & 255], 8) ^ kw[0];
|
||||
r1 = T0[t1 & 255] ^ Shift(T0[(t2 >> 8) & 255], 24) ^ Shift(T0[(r3 >> 16) & 255], 16) ^ Shift(T0[(t0 >> 24) & 255], 8) ^ kw[1];
|
||||
r2 = T0[t2 & 255] ^ Shift(T0[(r3 >> 8) & 255], 24) ^ Shift(T0[(t0 >> 16) & 255], 16) ^ Shift(T0[(t1 >> 24) & 255], 8) ^ kw[2];
|
||||
r3 = T0[r3 & 255] ^ Shift(T0[(t0 >> 8) & 255], 24) ^ Shift(T0[(t1 >> 16) & 255], 16) ^ Shift(T0[(t2 >> 24) & 255], 8) ^ kw[3];
|
||||
|
||||
// the final round's table is a simple function of S so we don't use a whole other four tables for it
|
||||
|
||||
kw = KW[r];
|
||||
C0 = (uint)S[r0 & 255] ^ (((uint)S[(r1 >> 8) & 255]) << 8) ^ (((uint)s[(r2 >> 16) & 255]) << 16) ^ (((uint)s[(r3 >> 24) & 255]) << 24) ^ kw[0];
|
||||
C1 = (uint)s[r1 & 255] ^ (((uint)S[(r2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)s[(r0 >> 24) & 255]) << 24) ^ kw[1];
|
||||
C2 = (uint)s[r2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(r0 >> 16) & 255]) << 16) ^ (((uint)S[(r1 >> 24) & 255]) << 24) ^ kw[2];
|
||||
C3 = (uint)s[r3 & 255] ^ (((uint)s[(r0 >> 8) & 255]) << 8) ^ (((uint)s[(r1 >> 16) & 255]) << 16) ^ (((uint)S[(r2 >> 24) & 255]) << 24) ^ kw[3];
|
||||
|
||||
Pack.UInt32_To_LE(C0, output);
|
||||
Pack.UInt32_To_LE(C1, output[4..]);
|
||||
Pack.UInt32_To_LE(C2, output[8..]);
|
||||
Pack.UInt32_To_LE(C3, output[12..]);
|
||||
}
|
||||
|
||||
private void DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output, uint[][] KW)
|
||||
{
|
||||
uint C0 = Pack.LE_To_UInt32(input);
|
||||
uint C1 = Pack.LE_To_UInt32(input[4..]);
|
||||
uint C2 = Pack.LE_To_UInt32(input[8..]);
|
||||
uint C3 = Pack.LE_To_UInt32(input[12..]);
|
||||
|
||||
uint[] kw = KW[ROUNDS];
|
||||
uint t0 = C0 ^ kw[0];
|
||||
uint t1 = C1 ^ kw[1];
|
||||
uint t2 = C2 ^ kw[2];
|
||||
|
||||
uint r0, r1, r2, r3 = C3 ^ kw[3];
|
||||
int r = ROUNDS - 1;
|
||||
while (r > 1)
|
||||
{
|
||||
kw = KW[r--];
|
||||
r0 = Tinv0[t0 & 255] ^ Shift(Tinv0[(r3 >> 8) & 255], 24) ^ Shift(Tinv0[(t2 >> 16) & 255], 16) ^ Shift(Tinv0[(t1 >> 24) & 255], 8) ^ kw[0];
|
||||
r1 = Tinv0[t1 & 255] ^ Shift(Tinv0[(t0 >> 8) & 255], 24) ^ Shift(Tinv0[(r3 >> 16) & 255], 16) ^ Shift(Tinv0[(t2 >> 24) & 255], 8) ^ kw[1];
|
||||
r2 = Tinv0[t2 & 255] ^ Shift(Tinv0[(t1 >> 8) & 255], 24) ^ Shift(Tinv0[(t0 >> 16) & 255], 16) ^ Shift(Tinv0[(r3 >> 24) & 255], 8) ^ kw[2];
|
||||
r3 = Tinv0[r3 & 255] ^ Shift(Tinv0[(t2 >> 8) & 255], 24) ^ Shift(Tinv0[(t1 >> 16) & 255], 16) ^ Shift(Tinv0[(t0 >> 24) & 255], 8) ^ kw[3];
|
||||
kw = KW[r--];
|
||||
t0 = Tinv0[r0 & 255] ^ Shift(Tinv0[(r3 >> 8) & 255], 24) ^ Shift(Tinv0[(r2 >> 16) & 255], 16) ^ Shift(Tinv0[(r1 >> 24) & 255], 8) ^ kw[0];
|
||||
t1 = Tinv0[r1 & 255] ^ Shift(Tinv0[(r0 >> 8) & 255], 24) ^ Shift(Tinv0[(r3 >> 16) & 255], 16) ^ Shift(Tinv0[(r2 >> 24) & 255], 8) ^ kw[1];
|
||||
t2 = Tinv0[r2 & 255] ^ Shift(Tinv0[(r1 >> 8) & 255], 24) ^ Shift(Tinv0[(r0 >> 16) & 255], 16) ^ Shift(Tinv0[(r3 >> 24) & 255], 8) ^ kw[2];
|
||||
r3 = Tinv0[r3 & 255] ^ Shift(Tinv0[(r2 >> 8) & 255], 24) ^ Shift(Tinv0[(r1 >> 16) & 255], 16) ^ Shift(Tinv0[(r0 >> 24) & 255], 8) ^ kw[3];
|
||||
}
|
||||
|
||||
kw = KW[1];
|
||||
r0 = Tinv0[t0 & 255] ^ Shift(Tinv0[(r3 >> 8) & 255], 24) ^ Shift(Tinv0[(t2 >> 16) & 255], 16) ^ Shift(Tinv0[(t1 >> 24) & 255], 8) ^ kw[0];
|
||||
r1 = Tinv0[t1 & 255] ^ Shift(Tinv0[(t0 >> 8) & 255], 24) ^ Shift(Tinv0[(r3 >> 16) & 255], 16) ^ Shift(Tinv0[(t2 >> 24) & 255], 8) ^ kw[1];
|
||||
r2 = Tinv0[t2 & 255] ^ Shift(Tinv0[(t1 >> 8) & 255], 24) ^ Shift(Tinv0[(t0 >> 16) & 255], 16) ^ Shift(Tinv0[(r3 >> 24) & 255], 8) ^ kw[2];
|
||||
r3 = Tinv0[r3 & 255] ^ Shift(Tinv0[(t2 >> 8) & 255], 24) ^ Shift(Tinv0[(t1 >> 16) & 255], 16) ^ Shift(Tinv0[(t0 >> 24) & 255], 8) ^ kw[3];
|
||||
|
||||
// the final round's table is a simple function of Si so we don't use a whole other four tables for it
|
||||
|
||||
kw = KW[0];
|
||||
C0 = (uint)Si[r0 & 255] ^ (((uint)s[(r3 >> 8) & 255]) << 8) ^ (((uint)s[(r2 >> 16) & 255]) << 16) ^ (((uint)Si[(r1 >> 24) & 255]) << 24) ^ kw[0];
|
||||
C1 = (uint)s[r1 & 255] ^ (((uint)s[(r0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ (((uint)s[(r2 >> 24) & 255]) << 24) ^ kw[1];
|
||||
C2 = (uint)s[r2 & 255] ^ (((uint)Si[(r1 >> 8) & 255]) << 8) ^ (((uint)Si[(r0 >> 16) & 255]) << 16) ^ (((uint)s[(r3 >> 24) & 255]) << 24) ^ kw[2];
|
||||
C3 = (uint)Si[r3 & 255] ^ (((uint)s[(r2 >> 8) & 255]) << 8) ^ (((uint)s[(r1 >> 16) & 255]) << 16) ^ (((uint)s[(r0 >> 24) & 255]) << 24) ^ kw[3];
|
||||
|
||||
Pack.UInt32_To_LE(C0, output);
|
||||
Pack.UInt32_To_LE(C1, output[4..]);
|
||||
Pack.UInt32_To_LE(C2, output[8..]);
|
||||
Pack.UInt32_To_LE(C3, output[12..]);
|
||||
}
|
||||
#else
|
||||
private void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff, uint[][] KW)
|
||||
{
|
||||
FastAesEngineHelper.EncryptBlock(input, inOff, output, outOff, KW, ROUNDS, T0, S, s);
|
||||
//uint C0 = Pack.LE_To_UInt32(input, inOff + 0);
|
||||
//uint C1 = Pack.LE_To_UInt32(input, inOff + 4);
|
||||
//uint C2 = Pack.LE_To_UInt32(input, inOff + 8);
|
||||
//uint C3 = Pack.LE_To_UInt32(input, inOff + 12);
|
||||
//
|
||||
//uint[] kw = KW[0];
|
||||
//uint t0 = C0 ^ kw[0];
|
||||
//uint t1 = C1 ^ kw[1];
|
||||
//uint t2 = C2 ^ kw[2];
|
||||
//
|
||||
//uint r0, r1, r2, r3 = C3 ^ kw[3];
|
||||
//int r = 1;
|
||||
//while (r < ROUNDS - 1)
|
||||
//{
|
||||
// kw = KW[r++];
|
||||
// r0 = T0[t0 & 255] ^ Shift(T0[(t1 >> 8) & 255], 24) ^ Shift(T0[(t2 >> 16) & 255], 16) ^ Shift(T0[(r3 >> 24) & 255], 8) ^ kw[0];
|
||||
// r1 = T0[t1 & 255] ^ Shift(T0[(t2 >> 8) & 255], 24) ^ Shift(T0[(r3 >> 16) & 255], 16) ^ Shift(T0[(t0 >> 24) & 255], 8) ^ kw[1];
|
||||
// r2 = T0[t2 & 255] ^ Shift(T0[(r3 >> 8) & 255], 24) ^ Shift(T0[(t0 >> 16) & 255], 16) ^ Shift(T0[(t1 >> 24) & 255], 8) ^ kw[2];
|
||||
// r3 = T0[r3 & 255] ^ Shift(T0[(t0 >> 8) & 255], 24) ^ Shift(T0[(t1 >> 16) & 255], 16) ^ Shift(T0[(t2 >> 24) & 255], 8) ^ kw[3];
|
||||
// kw = KW[r++];
|
||||
// t0 = T0[r0 & 255] ^ Shift(T0[(r1 >> 8) & 255], 24) ^ Shift(T0[(r2 >> 16) & 255], 16) ^ Shift(T0[(r3 >> 24) & 255], 8) ^ kw[0];
|
||||
// t1 = T0[r1 & 255] ^ Shift(T0[(r2 >> 8) & 255], 24) ^ Shift(T0[(r3 >> 16) & 255], 16) ^ Shift(T0[(r0 >> 24) & 255], 8) ^ kw[1];
|
||||
// t2 = T0[r2 & 255] ^ Shift(T0[(r3 >> 8) & 255], 24) ^ Shift(T0[(r0 >> 16) & 255], 16) ^ Shift(T0[(r1 >> 24) & 255], 8) ^ kw[2];
|
||||
// r3 = T0[r3 & 255] ^ Shift(T0[(r0 >> 8) & 255], 24) ^ Shift(T0[(r1 >> 16) & 255], 16) ^ Shift(T0[(r2 >> 24) & 255], 8) ^ kw[3];
|
||||
//}
|
||||
//
|
||||
//kw = KW[r++];
|
||||
//r0 = T0[t0 & 255] ^ Shift(T0[(t1 >> 8) & 255], 24) ^ Shift(T0[(t2 >> 16) & 255], 16) ^ Shift(T0[(r3 >> 24) & 255], 8) ^ kw[0];
|
||||
//r1 = T0[t1 & 255] ^ Shift(T0[(t2 >> 8) & 255], 24) ^ Shift(T0[(r3 >> 16) & 255], 16) ^ Shift(T0[(t0 >> 24) & 255], 8) ^ kw[1];
|
||||
//r2 = T0[t2 & 255] ^ Shift(T0[(r3 >> 8) & 255], 24) ^ Shift(T0[(t0 >> 16) & 255], 16) ^ Shift(T0[(t1 >> 24) & 255], 8) ^ kw[2];
|
||||
//r3 = T0[r3 & 255] ^ Shift(T0[(t0 >> 8) & 255], 24) ^ Shift(T0[(t1 >> 16) & 255], 16) ^ Shift(T0[(t2 >> 24) & 255], 8) ^ kw[3];
|
||||
//
|
||||
//// the final round's table is a simple function of S so we don't use a whole other four tables for it
|
||||
//
|
||||
//kw = KW[r];
|
||||
//C0 = (uint)S[r0 & 255] ^ (((uint)S[(r1 >> 8) & 255]) << 8) ^ (((uint)s[(r2 >> 16) & 255]) << 16) ^ (((uint)s[(r3 >> 24) & 255]) << 24) ^ kw[0];
|
||||
//C1 = (uint)s[r1 & 255] ^ (((uint)S[(r2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)s[(r0 >> 24) & 255]) << 24) ^ kw[1];
|
||||
//C2 = (uint)s[r2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(r0 >> 16) & 255]) << 16) ^ (((uint)S[(r1 >> 24) & 255]) << 24) ^ kw[2];
|
||||
//C3 = (uint)s[r3 & 255] ^ (((uint)s[(r0 >> 8) & 255]) << 8) ^ (((uint)s[(r1 >> 16) & 255]) << 16) ^ (((uint)S[(r2 >> 24) & 255]) << 24) ^ kw[3];
|
||||
//
|
||||
//Pack.UInt32_To_LE(C0, output, outOff + 0);
|
||||
//Pack.UInt32_To_LE(C1, output, outOff + 4);
|
||||
//Pack.UInt32_To_LE(C2, output, outOff + 8);
|
||||
//Pack.UInt32_To_LE(C3, output, outOff + 12);
|
||||
}
|
||||
|
||||
private void DecryptBlock(byte[] input, int inOff, byte[] output, int outOff, uint[][] KW)
|
||||
{
|
||||
uint C0 = Pack.LE_To_UInt32(input, inOff + 0);
|
||||
uint C1 = Pack.LE_To_UInt32(input, inOff + 4);
|
||||
uint C2 = Pack.LE_To_UInt32(input, inOff + 8);
|
||||
uint C3 = Pack.LE_To_UInt32(input, inOff + 12);
|
||||
|
||||
uint[] kw = KW[ROUNDS];
|
||||
uint t0 = C0 ^ kw[0];
|
||||
uint t1 = C1 ^ kw[1];
|
||||
uint t2 = C2 ^ kw[2];
|
||||
|
||||
uint r0, r1, r2, r3 = C3 ^ kw[3];
|
||||
int r = ROUNDS - 1;
|
||||
while (r > 1)
|
||||
{
|
||||
kw = KW[r--];
|
||||
r0 = Tinv0[t0 & 255] ^ Shift(Tinv0[(r3 >> 8) & 255], 24) ^ Shift(Tinv0[(t2 >> 16) & 255], 16) ^ Shift(Tinv0[(t1 >> 24) & 255], 8) ^ kw[0];
|
||||
r1 = Tinv0[t1 & 255] ^ Shift(Tinv0[(t0 >> 8) & 255], 24) ^ Shift(Tinv0[(r3 >> 16) & 255], 16) ^ Shift(Tinv0[(t2 >> 24) & 255], 8) ^ kw[1];
|
||||
r2 = Tinv0[t2 & 255] ^ Shift(Tinv0[(t1 >> 8) & 255], 24) ^ Shift(Tinv0[(t0 >> 16) & 255], 16) ^ Shift(Tinv0[(r3 >> 24) & 255], 8) ^ kw[2];
|
||||
r3 = Tinv0[r3 & 255] ^ Shift(Tinv0[(t2 >> 8) & 255], 24) ^ Shift(Tinv0[(t1 >> 16) & 255], 16) ^ Shift(Tinv0[(t0 >> 24) & 255], 8) ^ kw[3];
|
||||
kw = KW[r--];
|
||||
t0 = Tinv0[r0 & 255] ^ Shift(Tinv0[(r3 >> 8) & 255], 24) ^ Shift(Tinv0[(r2 >> 16) & 255], 16) ^ Shift(Tinv0[(r1 >> 24) & 255], 8) ^ kw[0];
|
||||
t1 = Tinv0[r1 & 255] ^ Shift(Tinv0[(r0 >> 8) & 255], 24) ^ Shift(Tinv0[(r3 >> 16) & 255], 16) ^ Shift(Tinv0[(r2 >> 24) & 255], 8) ^ kw[1];
|
||||
t2 = Tinv0[r2 & 255] ^ Shift(Tinv0[(r1 >> 8) & 255], 24) ^ Shift(Tinv0[(r0 >> 16) & 255], 16) ^ Shift(Tinv0[(r3 >> 24) & 255], 8) ^ kw[2];
|
||||
r3 = Tinv0[r3 & 255] ^ Shift(Tinv0[(r2 >> 8) & 255], 24) ^ Shift(Tinv0[(r1 >> 16) & 255], 16) ^ Shift(Tinv0[(r0 >> 24) & 255], 8) ^ kw[3];
|
||||
}
|
||||
|
||||
kw = KW[1];
|
||||
r0 = Tinv0[t0 & 255] ^ Shift(Tinv0[(r3 >> 8) & 255], 24) ^ Shift(Tinv0[(t2 >> 16) & 255], 16) ^ Shift(Tinv0[(t1 >> 24) & 255], 8) ^ kw[0];
|
||||
r1 = Tinv0[t1 & 255] ^ Shift(Tinv0[(t0 >> 8) & 255], 24) ^ Shift(Tinv0[(r3 >> 16) & 255], 16) ^ Shift(Tinv0[(t2 >> 24) & 255], 8) ^ kw[1];
|
||||
r2 = Tinv0[t2 & 255] ^ Shift(Tinv0[(t1 >> 8) & 255], 24) ^ Shift(Tinv0[(t0 >> 16) & 255], 16) ^ Shift(Tinv0[(r3 >> 24) & 255], 8) ^ kw[2];
|
||||
r3 = Tinv0[r3 & 255] ^ Shift(Tinv0[(t2 >> 8) & 255], 24) ^ Shift(Tinv0[(t1 >> 16) & 255], 16) ^ Shift(Tinv0[(t0 >> 24) & 255], 8) ^ kw[3];
|
||||
|
||||
// the final round's table is a simple function of Si so we don't use a whole other four tables for it
|
||||
|
||||
kw = KW[0];
|
||||
C0 = (uint)Si[r0 & 255] ^ (((uint)s[(r3 >> 8) & 255]) << 8) ^ (((uint)s[(r2 >> 16) & 255]) << 16) ^ (((uint)Si[(r1 >> 24) & 255]) << 24) ^ kw[0];
|
||||
C1 = (uint)s[r1 & 255] ^ (((uint)s[(r0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ (((uint)s[(r2 >> 24) & 255]) << 24) ^ kw[1];
|
||||
C2 = (uint)s[r2 & 255] ^ (((uint)Si[(r1 >> 8) & 255]) << 8) ^ (((uint)Si[(r0 >> 16) & 255]) << 16) ^ (((uint)s[(r3 >> 24) & 255]) << 24) ^ kw[2];
|
||||
C3 = (uint)Si[r3 & 255] ^ (((uint)s[(r2 >> 8) & 255]) << 8) ^ (((uint)s[(r1 >> 16) & 255]) << 16) ^ (((uint)s[(r0 >> 24) & 255]) << 24) ^ kw[3];
|
||||
|
||||
Pack.UInt32_To_LE(C0, output, outOff + 0);
|
||||
Pack.UInt32_To_LE(C1, output, outOff + 4);
|
||||
Pack.UInt32_To_LE(C2, output, outOff + 8);
|
||||
Pack.UInt32_To_LE(C3, output, outOff + 12);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
using System;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
internal static class FastAesEngineHelper
|
||||
{
|
||||
public unsafe static void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff, uint[][] KW, int ROUNDS, uint[] T0, byte[] S, byte[] s)
|
||||
{
|
||||
uint C0 = Pack.LE_To_UInt32(input, inOff + 0);
|
||||
uint C1 = Pack.LE_To_UInt32(input, inOff + 4);
|
||||
uint C2 = Pack.LE_To_UInt32(input, inOff + 8);
|
||||
uint C3 = Pack.LE_To_UInt32(input, inOff + 12);
|
||||
|
||||
uint[] kw = KW[0];
|
||||
uint t0 = C0 ^ kw[0];
|
||||
uint t1 = C1 ^ kw[1];
|
||||
uint t2 = C2 ^ kw[2];
|
||||
|
||||
uint r0, r1, r2, r3 = C3 ^ kw[3];
|
||||
int r = 1;
|
||||
|
||||
byte idx;
|
||||
uint tmp1, tmp2, tmp3;
|
||||
|
||||
fixed (uint* pT0 = T0)
|
||||
{
|
||||
while (r < ROUNDS - 1)
|
||||
{
|
||||
kw = KW[r++];
|
||||
|
||||
fixed (uint* pkw = kw)
|
||||
{
|
||||
idx = (byte)(t1 >> 8);
|
||||
tmp1 = (pT0[idx] >> 24) | (pT0[idx] << 8);
|
||||
|
||||
idx = (byte)(t2 >> 16);
|
||||
tmp2 = (pT0[idx] >> 16) | (pT0[idx] << 16);
|
||||
|
||||
idx = (byte)(r3 >> 24);
|
||||
tmp3 = (pT0[idx] >> 8) | (pT0[idx] << 24);
|
||||
r0 = pT0[t0 & 255] ^ tmp1 ^ tmp2 ^ tmp3 ^ pkw[0];
|
||||
|
||||
idx = (byte)(t2 >> 8);
|
||||
tmp1 = (pT0[idx] >> 24) | (pT0[idx] << 8);
|
||||
|
||||
idx = (byte)(r3 >> 16);
|
||||
tmp2 = (pT0[idx] >> 16) | (pT0[idx] << 16);
|
||||
|
||||
idx = (byte)(t0 >> 24);
|
||||
tmp3 = (pT0[idx] >> 8) | (pT0[idx] << 24);
|
||||
r1 = pT0[t1 & 255] ^ tmp1 ^ tmp2 ^ tmp3 ^ pkw[1];
|
||||
|
||||
idx = (byte)(r3 >> 8);
|
||||
tmp1 = (pT0[idx] >> 24) | (pT0[idx] << 8);
|
||||
|
||||
idx = (byte)(t0 >> 16);
|
||||
tmp2 = (pT0[idx] >> 16) | (pT0[idx] << 16);
|
||||
|
||||
idx = (byte)(t1 >> 24);
|
||||
tmp3 = (pT0[idx] >> 8) | (pT0[idx] << 24);
|
||||
|
||||
r2 = pT0[t2 & 255] ^ tmp1 ^ tmp2 ^ tmp3 ^ pkw[2];
|
||||
|
||||
idx = (byte)(t0 >> 8);
|
||||
tmp1 = (pT0[idx] >> 24) | (pT0[idx] << 8);
|
||||
|
||||
idx = (byte)(t1 >> 16);
|
||||
tmp2 = (pT0[idx] >> 16) | (pT0[idx] << 16);
|
||||
|
||||
idx = (byte)(t2 >> 24);
|
||||
tmp3 = (pT0[idx] >> 8) | (pT0[idx] << 24);
|
||||
|
||||
r3 = pT0[r3 & 255] ^ tmp1 ^ tmp2 ^ tmp3 ^ pkw[3];
|
||||
}
|
||||
|
||||
kw = KW[r++];
|
||||
|
||||
fixed (uint* pkw = kw)
|
||||
{
|
||||
idx = (byte)(r1 >> 8);
|
||||
tmp1 = (pT0[idx] >> 24) | (pT0[idx] << 8);
|
||||
|
||||
idx = (byte)(r2 >> 16);
|
||||
tmp2 = (pT0[idx] >> 16) | (pT0[idx] << 16);
|
||||
|
||||
idx = (byte)(r3 >> 24);
|
||||
tmp3 = (pT0[idx] >> 8) | (pT0[idx] << 24);
|
||||
t0 = pT0[r0 & 255] ^ tmp1 ^ tmp2 ^ tmp3 ^ pkw[0];
|
||||
|
||||
idx = (byte)(r2 >> 8);
|
||||
tmp1 = (pT0[idx] >> 24) | (pT0[idx] << 8);
|
||||
|
||||
idx = (byte)(r3 >> 16);
|
||||
tmp2 = (pT0[idx] >> 16) | (pT0[idx] << 16);
|
||||
|
||||
idx = (byte)(r0 >> 24);
|
||||
tmp3 = (pT0[idx] >> 8) | (pT0[idx] << 24);
|
||||
t1 = pT0[r1 & 255] ^ tmp1 ^ tmp2 ^ tmp3 ^ pkw[1];
|
||||
|
||||
idx = (byte)(r3 >> 8);
|
||||
tmp1 = (pT0[idx] >> 24) | (pT0[idx] << 8);
|
||||
|
||||
idx = (byte)(r0 >> 16);
|
||||
tmp2 = (pT0[idx] >> 16) | (pT0[idx] << 16);
|
||||
|
||||
idx = (byte)(r1 >> 24);
|
||||
tmp3 = (pT0[idx] >> 8) | (pT0[idx] << 24);
|
||||
|
||||
t2 = pT0[r2 & 255] ^ tmp1 ^ tmp2 ^ tmp3 ^ pkw[2];
|
||||
|
||||
idx = (byte)(r0 >> 8);
|
||||
tmp1 = (pT0[idx] >> 24) | (pT0[idx] << 8);
|
||||
|
||||
idx = (byte)(r1 >> 16);
|
||||
tmp2 = (pT0[idx] >> 16) | (pT0[idx] << 16);
|
||||
|
||||
idx = (byte)(r2 >> 24);
|
||||
tmp3 = (pT0[idx] >> 8) | (pT0[idx] << 24);
|
||||
r3 = pT0[r3 & 255] ^ tmp1 ^ tmp2 ^ tmp3 ^ pkw[3];
|
||||
}
|
||||
}
|
||||
|
||||
kw = KW[r++];
|
||||
|
||||
fixed (uint* pkw = kw)
|
||||
{
|
||||
idx = (byte)(t1 >> 8);
|
||||
tmp1 = (pT0[idx] >> 24) | (pT0[idx] << 8);
|
||||
|
||||
idx = (byte)(t2 >> 16);
|
||||
tmp2 = (pT0[idx] >> 16) | (pT0[idx] << 16);
|
||||
|
||||
idx = (byte)(r3 >> 24);
|
||||
tmp3 = (pT0[idx] >> 8) | (pT0[idx] << 24);
|
||||
r0 = pT0[t0 & 255] ^ tmp1 ^ tmp2 ^ tmp3 ^ pkw[0];
|
||||
|
||||
idx = (byte)(t2 >> 8);
|
||||
tmp1 = (pT0[idx] >> 24) | (pT0[idx] << 8);
|
||||
|
||||
idx = (byte)(r3 >> 16);
|
||||
tmp2 = (pT0[idx] >> 16) | (pT0[idx] << 16);
|
||||
|
||||
idx = (byte)(t0 >> 24);
|
||||
tmp3 = (pT0[idx] >> 8) | (pT0[idx] << 24);
|
||||
|
||||
r1 = pT0[t1 & 255] ^ tmp1 ^ tmp2 ^ tmp3 ^ pkw[1];
|
||||
|
||||
idx = (byte)(r3 >> 8);
|
||||
tmp1 = (pT0[idx] >> 24) | (pT0[idx] << 8);
|
||||
|
||||
idx = (byte)(t0 >> 16);
|
||||
tmp2 = (pT0[idx] >> 16) | (pT0[idx] << 16);
|
||||
|
||||
idx = (byte)(t1 >> 24);
|
||||
tmp3 = (pT0[idx] >> 8) | (pT0[idx] << 24);
|
||||
|
||||
r2 = pT0[t2 & 255] ^ tmp1 ^ tmp2 ^ tmp3 ^ pkw[2];
|
||||
|
||||
idx = (byte)(t0 >> 8);
|
||||
tmp1 = (pT0[idx] >> 24) | (pT0[idx] << 8);
|
||||
|
||||
idx = (byte)(t1 >> 16);
|
||||
tmp2 = (pT0[idx] >> 16) | (pT0[idx] << 16);
|
||||
|
||||
idx = (byte)(t2 >> 24);
|
||||
tmp3 = (pT0[idx] >> 8) | (pT0[idx] << 24);
|
||||
|
||||
r3 = pT0[r3 & 255] ^ tmp1 ^ tmp2 ^ tmp3 ^ pkw[3];
|
||||
}
|
||||
|
||||
// the final round's table is a simple function of S so we don't use a whole other four tables for it
|
||||
|
||||
kw = KW[r];
|
||||
|
||||
fixed (byte* pS = S, ps = s)
|
||||
fixed (uint* pkw = kw)
|
||||
{
|
||||
C0 = (uint)pS[(byte)r0] ^ (((uint)pS[(byte)(r1 >> 8)]) << 8) ^ (((uint)ps[(byte)(r2 >> 16)]) << 16) ^ (((uint)ps[(byte)(r3 >> 24)]) << 24) ^ pkw[0];
|
||||
C1 = (uint)ps[(byte)r1] ^ (((uint)pS[(byte)(r2 >> 8)]) << 8) ^ (((uint)pS[(byte)(r3 >> 16)]) << 16) ^ (((uint)ps[(byte)(r0 >> 24)]) << 24) ^ pkw[1];
|
||||
C2 = (uint)ps[(byte)r2] ^ (((uint)pS[(byte)(r3 >> 8)]) << 8) ^ (((uint)pS[(byte)(r0 >> 16)]) << 16) ^ (((uint)pS[(byte)(r1 >> 24)]) << 24) ^ pkw[2];
|
||||
C3 = (uint)ps[(byte)r3] ^ (((uint)ps[(byte)(r0 >> 8)]) << 8) ^ (((uint)ps[(byte)(r1 >> 16)]) << 16) ^ (((uint)pS[(byte)(r2 >> 24)]) << 24) ^ pkw[3];
|
||||
}
|
||||
}
|
||||
|
||||
Pack.UInt32_To_LE(C0, output, outOff + 0);
|
||||
Pack.UInt32_To_LE(C1, output, outOff + 4);
|
||||
Pack.UInt32_To_LE(C2, output, outOff + 8);
|
||||
Pack.UInt32_To_LE(C3, output, outOff + 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Engines;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Macs;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto.Impl;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
public sealed class FastBcChaCha20Poly1305
|
||||
: TlsAeadCipherImpl
|
||||
{
|
||||
private static readonly byte[] Zeroes = new byte[15];
|
||||
|
||||
private readonly FastChaCha7539Engine m_cipher = new FastChaCha7539Engine();
|
||||
private readonly FastPoly1305 m_mac = new FastPoly1305();
|
||||
|
||||
private readonly bool m_isEncrypting;
|
||||
|
||||
private int m_additionalDataLength;
|
||||
|
||||
public FastBcChaCha20Poly1305(bool isEncrypting)
|
||||
{
|
||||
this.m_isEncrypting = isEncrypting;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
unsafe
|
||||
#endif
|
||||
public int DoFinal(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset)
|
||||
{
|
||||
if (m_isEncrypting)
|
||||
{
|
||||
int ciphertextLength = inputLength;
|
||||
|
||||
m_cipher.DoFinal(input, inputOffset, inputLength, output, outputOffset);
|
||||
int outputLength = inputLength;
|
||||
|
||||
if (ciphertextLength != outputLength)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
UpdateMac(output, outputOffset, ciphertextLength);
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
Span<byte> lengths = stackalloc byte[16];
|
||||
|
||||
Pack.UInt64_To_LE((ulong)m_additionalDataLength, lengths);
|
||||
Pack.UInt64_To_LE((ulong)ciphertextLength, lengths[8..]);
|
||||
|
||||
m_mac.BlockUpdate(lengths);
|
||||
m_mac.DoFinal(output.AsSpan(outputOffset + ciphertextLength));
|
||||
#else
|
||||
byte[] lengths = BufferPool.Get(16, true);
|
||||
using (var _ = new PooledBuffer(lengths))
|
||||
{
|
||||
Pack.UInt64_To_LE((ulong)m_additionalDataLength, lengths, 0);
|
||||
Pack.UInt64_To_LE((ulong)ciphertextLength, lengths, 8);
|
||||
|
||||
m_mac.BlockUpdate(lengths, 0, 16);
|
||||
m_mac.DoFinal(output, outputOffset + ciphertextLength);
|
||||
}
|
||||
#endif
|
||||
|
||||
return ciphertextLength + 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
int ciphertextLength = inputLength - 16;
|
||||
|
||||
UpdateMac(input, inputOffset, ciphertextLength);
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
Span<byte> expectedMac = stackalloc byte[16];
|
||||
|
||||
Pack.UInt64_To_LE((ulong)m_additionalDataLength, expectedMac);
|
||||
Pack.UInt64_To_LE((ulong)ciphertextLength, expectedMac[8..]);
|
||||
|
||||
m_mac.BlockUpdate(expectedMac);
|
||||
m_mac.DoFinal(expectedMac);
|
||||
|
||||
bool badMac = !TlsUtilities.ConstantTimeAreEqual(16, expectedMac, 0, input, inputOffset + ciphertextLength);
|
||||
|
||||
if (badMac)
|
||||
throw new TlsFatalAlert(AlertDescription.bad_record_mac);
|
||||
#else
|
||||
byte[] expectedMac = BufferPool.Get(16, true);
|
||||
using (var _ = new PooledBuffer(expectedMac))
|
||||
{
|
||||
Pack.UInt64_To_LE((ulong)m_additionalDataLength, expectedMac, 0);
|
||||
Pack.UInt64_To_LE((ulong)ciphertextLength, expectedMac, 8);
|
||||
|
||||
m_mac.BlockUpdate(expectedMac, 0, 16);
|
||||
m_mac.DoFinal(expectedMac, 0);
|
||||
|
||||
bool badMac = !TlsUtilities.ConstantTimeAreEqual(16, expectedMac, 0, input, inputOffset + ciphertextLength);
|
||||
|
||||
if (badMac)
|
||||
throw new TlsFatalAlert(AlertDescription.bad_record_mac);
|
||||
}
|
||||
#endif
|
||||
|
||||
m_cipher.DoFinal(input, inputOffset, ciphertextLength, output, outputOffset);
|
||||
int outputLength = ciphertextLength;
|
||||
|
||||
if (ciphertextLength != outputLength)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
return ciphertextLength;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetOutputSize(int inputLength)
|
||||
{
|
||||
return m_isEncrypting ? inputLength + 16 : inputLength - 16;
|
||||
}
|
||||
|
||||
public void Init(byte[] nonce, int macSize, byte[] additionalData)
|
||||
{
|
||||
if (nonce == null || nonce.Length != 12 || macSize != 16)
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
|
||||
m_cipher.Init(m_isEncrypting, new ParametersWithIV(null, nonce));
|
||||
InitMac();
|
||||
if (additionalData == null)
|
||||
{
|
||||
this.m_additionalDataLength = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.m_additionalDataLength = additionalData.Length;
|
||||
UpdateMac(additionalData, 0, additionalData.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
m_cipher.Reset();
|
||||
m_mac.Reset();
|
||||
}
|
||||
|
||||
public void SetKey(byte[] key, int keyOff, int keyLen)
|
||||
{
|
||||
KeyParameter cipherKey = new KeyParameter(key, keyOff, keyLen);
|
||||
m_cipher.Init(m_isEncrypting, new ParametersWithIV(cipherKey, Zeroes, 0, 12));
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public void SetKey(ReadOnlySpan<byte> key)
|
||||
{
|
||||
KeyParameter cipherKey = new KeyParameter(key);
|
||||
m_cipher.Init(m_isEncrypting, new ParametersWithIV(cipherKey, Zeroes[..12]));
|
||||
}
|
||||
#endif
|
||||
|
||||
byte[] firstBlock = new byte[64];
|
||||
private void InitMac()
|
||||
{
|
||||
m_cipher.ProcessBytes(firstBlock, 0, 64, firstBlock, 0);
|
||||
m_mac.Init(new KeyParameter(firstBlock, 0, 32));
|
||||
Array.Clear(firstBlock, 0, firstBlock.Length);
|
||||
}
|
||||
|
||||
private void UpdateMac(byte[] buf, int off, int len)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
m_mac.BlockUpdate(buf.AsSpan(off, len));
|
||||
#else
|
||||
m_mac.BlockUpdate(buf, off, len);
|
||||
#endif
|
||||
|
||||
int partial = len % 16;
|
||||
if (partial != 0)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
m_mac.BlockUpdate(Zeroes.AsSpan(0, 16 - partial));
|
||||
#else
|
||||
m_mac.BlockUpdate(Zeroes, 0, 16 - partial);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
|
||||
using BestHTTP.Connections.TLS.Crypto.Impl;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Modes;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto
|
||||
{
|
||||
/**
|
||||
* implements Cipher-Block-Chaining (CBC) mode on top of a simple cipher.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
[BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
|
||||
public sealed class FastCbcBlockCipher
|
||||
: IBlockCipherMode
|
||||
{
|
||||
private byte[] IV, cbcV, cbcNextV;
|
||||
private int blockSize;
|
||||
private IBlockCipher cipher;
|
||||
private bool encrypting;
|
||||
|
||||
/**
|
||||
* Basic constructor.
|
||||
*
|
||||
* @param cipher the block cipher to be used as the basis of chaining.
|
||||
*/
|
||||
public FastCbcBlockCipher(
|
||||
IBlockCipher cipher)
|
||||
{
|
||||
this.cipher = cipher;
|
||||
this.blockSize = cipher.GetBlockSize();
|
||||
|
||||
this.IV = new byte[blockSize];
|
||||
this.cbcV = new byte[blockSize];
|
||||
this.cbcNextV = new byte[blockSize];
|
||||
}
|
||||
|
||||
/**
|
||||
* return the underlying block cipher that we are wrapping.
|
||||
*
|
||||
* @return the underlying block cipher that we are wrapping.
|
||||
*/
|
||||
public IBlockCipher UnderlyingCipher => cipher;
|
||||
|
||||
/**
|
||||
* Initialise the cipher and, possibly, the initialisation vector (IV).
|
||||
* If an IV isn't passed as part of the parameter, the IV will be all zeros.
|
||||
*
|
||||
* @param forEncryption if true the cipher is initialised for
|
||||
* encryption, if false for decryption.
|
||||
* @param param the key and other data required by the cipher.
|
||||
* @exception ArgumentException if the parameters argument is
|
||||
* inappropriate.
|
||||
*/
|
||||
public void Init(bool forEncryption, ICipherParameters parameters)
|
||||
{
|
||||
bool oldEncrypting = this.encrypting;
|
||||
|
||||
this.encrypting = forEncryption;
|
||||
|
||||
if (parameters is ParametersWithIV ivParam)
|
||||
{
|
||||
byte[] iv = ivParam.GetIV();
|
||||
|
||||
if (iv.Length != blockSize)
|
||||
throw new ArgumentException("initialisation vector must be the same length as block size");
|
||||
|
||||
Array.Copy(iv, 0, IV, 0, iv.Length);
|
||||
|
||||
parameters = ivParam.Parameters;
|
||||
}
|
||||
|
||||
Reset();
|
||||
|
||||
// if null it's an IV changed only.
|
||||
if (parameters != null)
|
||||
{
|
||||
cipher.Init(encrypting, parameters);
|
||||
}
|
||||
else if (oldEncrypting != encrypting)
|
||||
{
|
||||
throw new ArgumentException("cannot change encrypting state without providing key.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return the algorithm name and mode.
|
||||
*
|
||||
* @return the name of the underlying algorithm followed by "/CBC".
|
||||
*/
|
||||
public string AlgorithmName
|
||||
{
|
||||
get { return cipher.AlgorithmName + "/CBC"; }
|
||||
}
|
||||
|
||||
public bool IsPartialBlockOkay
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/**
|
||||
* return the block size of the underlying cipher.
|
||||
*
|
||||
* @return the block size of the underlying cipher.
|
||||
*/
|
||||
public int GetBlockSize()
|
||||
{
|
||||
return cipher.GetBlockSize();
|
||||
}
|
||||
|
||||
public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
return encrypting
|
||||
? EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff))
|
||||
: DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
|
||||
#else
|
||||
return encrypting
|
||||
? EncryptBlock(input, inOff, output, outOff)
|
||||
: DecryptBlock(input, inOff, output, outOff);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
|
||||
{
|
||||
return encrypting
|
||||
? EncryptBlock(input, output)
|
||||
: DecryptBlock(input, output);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* reset the chaining vector back to the IV and reset the underlying
|
||||
* cipher.
|
||||
*/
|
||||
public void Reset()
|
||||
{
|
||||
Array.Copy(IV, 0, cbcV, 0, IV.Length);
|
||||
Array.Clear(cbcNextV, 0, cbcNextV.Length);
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
private int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
|
||||
{
|
||||
Check.DataLength(input, blockSize, "input buffer too short");
|
||||
Check.OutputLength(output, blockSize, "output buffer too short");
|
||||
|
||||
for (int i = 0; i < blockSize; i++)
|
||||
{
|
||||
cbcV[i] ^= input[i];
|
||||
}
|
||||
|
||||
int length = cipher.ProcessBlock(cbcV, output);
|
||||
|
||||
output[..blockSize].CopyTo(cbcV);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
|
||||
{
|
||||
Check.DataLength(input, blockSize, "input buffer too short");
|
||||
Check.OutputLength(output, blockSize, "output buffer too short");
|
||||
|
||||
input[..blockSize].CopyTo(cbcNextV);
|
||||
|
||||
int length = cipher.ProcessBlock(input, output);
|
||||
|
||||
for (int i = 0; i < blockSize; i++)
|
||||
{
|
||||
output[i] ^= cbcV[i];
|
||||
}
|
||||
|
||||
byte[] tmp = cbcV;
|
||||
cbcV = cbcNextV;
|
||||
cbcNextV = tmp;
|
||||
|
||||
return length;
|
||||
}
|
||||
#else
|
||||
private int EncryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
|
||||
{
|
||||
Check.DataLength(input, inOff, blockSize, "input buffer too short");
|
||||
Check.OutputLength(outBytes, outOff, blockSize, "output buffer too short");
|
||||
|
||||
for (int i = 0; i < blockSize; i++)
|
||||
{
|
||||
cbcV[i] ^= input[inOff + i];
|
||||
}
|
||||
|
||||
int length = cipher.ProcessBlock(cbcV, 0, outBytes, outOff);
|
||||
|
||||
Array.Copy(outBytes, outOff, cbcV, 0, cbcV.Length);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private int DecryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
|
||||
{
|
||||
Check.DataLength(input, inOff, blockSize, "input buffer too short");
|
||||
Check.OutputLength(outBytes, outOff, blockSize, "output buffer too short");
|
||||
|
||||
Array.Copy(input, inOff, cbcNextV, 0, blockSize);
|
||||
|
||||
int length = cipher.ProcessBlock(input, inOff, outBytes, outOff);
|
||||
|
||||
for (int i = 0; i < blockSize; i++)
|
||||
{
|
||||
outBytes[outOff + i] ^= cbcV[i];
|
||||
}
|
||||
|
||||
byte[] tmp = cbcV;
|
||||
cbcV = cbcNextV;
|
||||
cbcNextV = tmp;
|
||||
|
||||
return length;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,661 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Macs;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Modes;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
/**
|
||||
* Implements the Counter with Cipher Block Chaining mode (CCM) detailed in
|
||||
* NIST Special Publication 800-38C.
|
||||
* <p>
|
||||
* <b>Note</b>: this mode is a packet mode - it needs all the data up front.
|
||||
* </p>
|
||||
*/
|
||||
public class FastCcmBlockCipher
|
||||
: IAeadBlockCipher
|
||||
{
|
||||
private static readonly int BlockSize = 16;
|
||||
|
||||
private readonly IBlockCipher cipher;
|
||||
private readonly byte[] macBlock;
|
||||
private bool forEncryption;
|
||||
private byte[] nonce;
|
||||
private byte[] initialAssociatedText;
|
||||
private int macSize;
|
||||
private ICipherParameters keyParam;
|
||||
private readonly MemoryStream associatedText = new MemoryStream();
|
||||
private readonly MemoryStream data = new MemoryStream();
|
||||
|
||||
/**
|
||||
* Basic constructor.
|
||||
*
|
||||
* @param cipher the block cipher to be used.
|
||||
*/
|
||||
public FastCcmBlockCipher(
|
||||
IBlockCipher cipher)
|
||||
{
|
||||
this.cipher = cipher;
|
||||
this.macBlock = new byte[BlockSize];
|
||||
|
||||
if (cipher.GetBlockSize() != BlockSize)
|
||||
throw new ArgumentException("cipher required with a block size of " + BlockSize + ".");
|
||||
}
|
||||
|
||||
/**
|
||||
* return the underlying block cipher that we are wrapping.
|
||||
*
|
||||
* @return the underlying block cipher that we are wrapping.
|
||||
*/
|
||||
public virtual IBlockCipher UnderlyingCipher => cipher;
|
||||
|
||||
public virtual void Init(bool forEncryption, ICipherParameters parameters)
|
||||
{
|
||||
this.forEncryption = forEncryption;
|
||||
|
||||
ICipherParameters cipherParameters;
|
||||
if (parameters is AeadParameters aeadParameters)
|
||||
{
|
||||
nonce = aeadParameters.GetNonce();
|
||||
initialAssociatedText = aeadParameters.GetAssociatedText();
|
||||
macSize = GetMacSize(forEncryption, aeadParameters.MacSize);
|
||||
cipherParameters = aeadParameters.Key;
|
||||
}
|
||||
else if (parameters is ParametersWithIV parametersWithIV)
|
||||
{
|
||||
nonce = parametersWithIV.GetIV();
|
||||
initialAssociatedText = null;
|
||||
macSize = GetMacSize(forEncryption, 64);
|
||||
cipherParameters = parametersWithIV.Parameters;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("invalid parameters passed to CCM");
|
||||
}
|
||||
|
||||
// NOTE: Very basic support for key re-use, but no performance gain from it
|
||||
if (cipherParameters != null)
|
||||
{
|
||||
keyParam = cipherParameters;
|
||||
}
|
||||
|
||||
if (nonce == null || nonce.Length < 7 || nonce.Length > 13)
|
||||
throw new ArgumentException("nonce must have length from 7 to 13 octets");
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public virtual string AlgorithmName => cipher.AlgorithmName + "/CCM";
|
||||
|
||||
public virtual int GetBlockSize()
|
||||
{
|
||||
return cipher.GetBlockSize();
|
||||
}
|
||||
|
||||
public virtual void ProcessAadByte(byte input)
|
||||
{
|
||||
associatedText.WriteByte(input);
|
||||
}
|
||||
|
||||
public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len)
|
||||
{
|
||||
// TODO: Process AAD online
|
||||
associatedText.Write(inBytes, inOff, len);
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public virtual void ProcessAadBytes(ReadOnlySpan<byte> input)
|
||||
{
|
||||
// TODO: Process AAD online
|
||||
associatedText.Write(input);
|
||||
}
|
||||
#endif
|
||||
|
||||
public virtual int ProcessByte(byte input, byte[] outBytes, int outOff)
|
||||
{
|
||||
data.WriteByte(input);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public virtual int ProcessByte(byte input, Span<byte> output)
|
||||
{
|
||||
data.WriteByte(input);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
public virtual int ProcessBytes(byte[] inBytes, int inOff, int inLen, byte[] outBytes, int outOff)
|
||||
{
|
||||
Check.DataLength(inBytes, inOff, inLen, "input buffer too short");
|
||||
|
||||
data.Write(inBytes, inOff, inLen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
|
||||
{
|
||||
data.Write(input);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
public virtual int DoFinal(byte[] outBytes, int outOff)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
return DoFinal(outBytes.AsSpan(outOff));
|
||||
#else
|
||||
byte[] input = data.GetBuffer();
|
||||
int inLen = Convert.ToInt32(data.Length);
|
||||
|
||||
int len = ProcessPacket(input, 0, inLen, outBytes, outOff);
|
||||
|
||||
Reset();
|
||||
|
||||
return len;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public virtual int DoFinal(Span<byte> output)
|
||||
{
|
||||
byte[] input = data.GetBuffer();
|
||||
int inLen = Convert.ToInt32(data.Length);
|
||||
|
||||
int len = ProcessPacket(input.AsSpan(0, inLen), output);
|
||||
|
||||
Reset();
|
||||
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
associatedText.SetLength(0);
|
||||
data.SetLength(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array containing the mac calculated as part of the
|
||||
* last encrypt or decrypt operation.
|
||||
*
|
||||
* @return the last mac calculated.
|
||||
*/
|
||||
public virtual byte[] GetMac()
|
||||
{
|
||||
return Arrays.CopyOfRange(macBlock, 0, macSize);
|
||||
}
|
||||
|
||||
public virtual int GetUpdateOutputSize(int len)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public virtual int GetOutputSize(int len)
|
||||
{
|
||||
int totalData = Convert.ToInt32(data.Length) + len;
|
||||
|
||||
if (forEncryption)
|
||||
{
|
||||
return totalData + macSize;
|
||||
}
|
||||
|
||||
return totalData < macSize ? 0 : totalData - macSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a packet of data for either CCM decryption or encryption.
|
||||
*
|
||||
* @param in data for processing.
|
||||
* @param inOff offset at which data starts in the input array.
|
||||
* @param inLen length of the data in the input array.
|
||||
* @return a byte array containing the processed input..
|
||||
* @throws IllegalStateException if the cipher is not appropriately set up.
|
||||
* @throws InvalidCipherTextException if the input data is truncated or the mac check fails.
|
||||
*/
|
||||
public virtual byte[] ProcessPacket(byte[] input, int inOff, int inLen)
|
||||
{
|
||||
byte[] output;
|
||||
|
||||
if (forEncryption)
|
||||
{
|
||||
output = new byte[inLen + macSize];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inLen < macSize)
|
||||
throw new InvalidCipherTextException("data too short");
|
||||
|
||||
output = new byte[inLen - macSize];
|
||||
}
|
||||
|
||||
ProcessPacket(input, inOff, inLen, output, 0);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a packet of data for either CCM decryption or encryption.
|
||||
*
|
||||
* @param in data for processing.
|
||||
* @param inOff offset at which data starts in the input array.
|
||||
* @param inLen length of the data in the input array.
|
||||
* @param output output array.
|
||||
* @param outOff offset into output array to start putting processed bytes.
|
||||
* @return the number of bytes added to output.
|
||||
* @throws IllegalStateException if the cipher is not appropriately set up.
|
||||
* @throws InvalidCipherTextException if the input data is truncated or the mac check fails.
|
||||
* @throws DataLengthException if output buffer too short.
|
||||
*/
|
||||
public virtual int ProcessPacket(byte[] input, int inOff, int inLen, byte[] output, int outOff)
|
||||
{
|
||||
// TODO: handle null keyParam (e.g. via RepeatedKeySpec)
|
||||
// Need to keep the CTR and CBC Mac parts around and reset
|
||||
if (keyParam == null)
|
||||
throw new InvalidOperationException("CCM cipher unitialized.");
|
||||
|
||||
int n = nonce.Length;
|
||||
int q = 15 - n;
|
||||
if (q < 4)
|
||||
{
|
||||
int limitLen = 1 << (8 * q);
|
||||
if (inLen >= limitLen)
|
||||
throw new InvalidOperationException("CCM packet too large for choice of q.");
|
||||
}
|
||||
|
||||
byte[] iv = new byte[BlockSize];
|
||||
iv[0] = (byte)((q - 1) & 0x7);
|
||||
nonce.CopyTo(iv, 1);
|
||||
|
||||
IBlockCipher ctrCipher = new FastSicBlockCipher(cipher);
|
||||
ctrCipher.Init(forEncryption, new ParametersWithIV(keyParam, iv));
|
||||
|
||||
int outputLen;
|
||||
int inIndex = inOff;
|
||||
int outIndex = outOff;
|
||||
|
||||
if (forEncryption)
|
||||
{
|
||||
outputLen = inLen + macSize;
|
||||
Check.OutputLength(output, outOff, outputLen, "Output buffer too short.");
|
||||
|
||||
CalculateMac(input, inOff, inLen, macBlock);
|
||||
|
||||
byte[] encMac = new byte[BlockSize];
|
||||
ctrCipher.ProcessBlock(macBlock, 0, encMac, 0); // S0
|
||||
|
||||
while (inIndex < (inOff + inLen - BlockSize)) // S1...
|
||||
{
|
||||
ctrCipher.ProcessBlock(input, inIndex, output, outIndex);
|
||||
outIndex += BlockSize;
|
||||
inIndex += BlockSize;
|
||||
}
|
||||
|
||||
byte[] block = new byte[BlockSize];
|
||||
|
||||
Array.Copy(input, inIndex, block, 0, inLen + inOff - inIndex);
|
||||
|
||||
ctrCipher.ProcessBlock(block, 0, block, 0);
|
||||
|
||||
Array.Copy(block, 0, output, outIndex, inLen + inOff - inIndex);
|
||||
|
||||
Array.Copy(encMac, 0, output, outOff + inLen, macSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inLen < macSize)
|
||||
throw new InvalidCipherTextException("data too short");
|
||||
|
||||
outputLen = inLen - macSize;
|
||||
Check.OutputLength(output, outOff, outputLen, "Output buffer too short.");
|
||||
|
||||
Array.Copy(input, inOff + outputLen, macBlock, 0, macSize);
|
||||
|
||||
ctrCipher.ProcessBlock(macBlock, 0, macBlock, 0);
|
||||
|
||||
for (int i = macSize; i != macBlock.Length; i++)
|
||||
{
|
||||
macBlock[i] = 0;
|
||||
}
|
||||
|
||||
while (inIndex < (inOff + outputLen - BlockSize))
|
||||
{
|
||||
ctrCipher.ProcessBlock(input, inIndex, output, outIndex);
|
||||
outIndex += BlockSize;
|
||||
inIndex += BlockSize;
|
||||
}
|
||||
|
||||
byte[] block = new byte[BlockSize];
|
||||
|
||||
Array.Copy(input, inIndex, block, 0, outputLen - (inIndex - inOff));
|
||||
|
||||
ctrCipher.ProcessBlock(block, 0, block, 0);
|
||||
|
||||
Array.Copy(block, 0, output, outIndex, outputLen - (inIndex - inOff));
|
||||
|
||||
byte[] calculatedMacBlock = new byte[BlockSize];
|
||||
|
||||
CalculateMac(output, outOff, outputLen, calculatedMacBlock);
|
||||
|
||||
if (!Arrays.ConstantTimeAreEqual(macBlock, calculatedMacBlock))
|
||||
throw new InvalidCipherTextException("mac check in CCM failed");
|
||||
}
|
||||
|
||||
return outputLen;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public virtual int ProcessPacket(ReadOnlySpan<byte> input, Span<byte> output)
|
||||
{
|
||||
int inLen = input.Length;
|
||||
|
||||
// TODO: handle null keyParam (e.g. via RepeatedKeySpec)
|
||||
// Need to keep the CTR and CBC Mac parts around and reset
|
||||
if (keyParam == null)
|
||||
throw new InvalidOperationException("CCM cipher unitialized.");
|
||||
|
||||
int n = nonce.Length;
|
||||
int q = 15 - n;
|
||||
if (q < 4)
|
||||
{
|
||||
int limitLen = 1 << (8 * q);
|
||||
if (inLen >= limitLen)
|
||||
throw new InvalidOperationException("CCM packet too large for choice of q.");
|
||||
}
|
||||
|
||||
byte[] iv = new byte[BlockSize];
|
||||
iv[0] = (byte)((q - 1) & 0x7);
|
||||
nonce.CopyTo(iv, 1);
|
||||
|
||||
IBlockCipher ctrCipher = new SicBlockCipher(cipher);
|
||||
ctrCipher.Init(forEncryption, new ParametersWithIV(keyParam, iv));
|
||||
|
||||
int outputLen;
|
||||
int index = 0;
|
||||
Span<byte> block = stackalloc byte[BlockSize];
|
||||
|
||||
if (forEncryption)
|
||||
{
|
||||
outputLen = inLen + macSize;
|
||||
Check.OutputLength(output, outputLen, "output buffer too short");
|
||||
|
||||
CalculateMac(input, macBlock);
|
||||
|
||||
byte[] encMac = new byte[BlockSize];
|
||||
ctrCipher.ProcessBlock(macBlock, encMac); // S0
|
||||
|
||||
while (index < (inLen - BlockSize)) // S1...
|
||||
{
|
||||
ctrCipher.ProcessBlock(input[index..], output[index..]);
|
||||
index += BlockSize;
|
||||
}
|
||||
|
||||
input[index..].CopyTo(block);
|
||||
|
||||
ctrCipher.ProcessBlock(block, block);
|
||||
|
||||
block[..(inLen - index)].CopyTo(output[index..]);
|
||||
|
||||
encMac.AsSpan(0, macSize).CopyTo(output[inLen..]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inLen < macSize)
|
||||
throw new InvalidCipherTextException("data too short");
|
||||
|
||||
outputLen = inLen - macSize;
|
||||
Check.OutputLength(output, outputLen, "output buffer too short");
|
||||
|
||||
input[outputLen..].CopyTo(macBlock);
|
||||
|
||||
ctrCipher.ProcessBlock(macBlock, macBlock);
|
||||
|
||||
for (int i = macSize; i != macBlock.Length; i++)
|
||||
{
|
||||
macBlock[i] = 0;
|
||||
}
|
||||
|
||||
while (index < (outputLen - BlockSize))
|
||||
{
|
||||
ctrCipher.ProcessBlock(input[index..], output[index..]);
|
||||
index += BlockSize;
|
||||
}
|
||||
|
||||
input[index..outputLen].CopyTo(block);
|
||||
|
||||
ctrCipher.ProcessBlock(block, block);
|
||||
|
||||
block[..(outputLen - index)].CopyTo(output[index..]);
|
||||
|
||||
Span<byte> calculatedMacBlock = stackalloc byte[BlockSize];
|
||||
|
||||
CalculateMac(output[..outputLen], calculatedMacBlock);
|
||||
|
||||
if (!Arrays.ConstantTimeAreEqual(macBlock, calculatedMacBlock))
|
||||
throw new InvalidCipherTextException("mac check in CCM failed");
|
||||
}
|
||||
|
||||
return outputLen;
|
||||
}
|
||||
#endif
|
||||
|
||||
private int CalculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
return CalculateMac(data.AsSpan(dataOff, dataLen), macBlock);
|
||||
#else
|
||||
IMac cMac = new CbcBlockCipherMac(cipher, macSize * 8);
|
||||
|
||||
cMac.Init(keyParam);
|
||||
|
||||
//
|
||||
// build b0
|
||||
//
|
||||
byte[] b0 = new byte[16];
|
||||
|
||||
if (HasAssociatedText())
|
||||
{
|
||||
b0[0] |= 0x40;
|
||||
}
|
||||
|
||||
b0[0] |= (byte)((((cMac.GetMacSize() - 2) / 2) & 0x7) << 3);
|
||||
|
||||
b0[0] |= (byte)(((15 - nonce.Length) - 1) & 0x7);
|
||||
|
||||
Array.Copy(nonce, 0, b0, 1, nonce.Length);
|
||||
|
||||
int q = dataLen;
|
||||
int count = 1;
|
||||
while (q > 0)
|
||||
{
|
||||
b0[b0.Length - count] = (byte)(q & 0xff);
|
||||
q >>= 8;
|
||||
count++;
|
||||
}
|
||||
|
||||
cMac.BlockUpdate(b0, 0, b0.Length);
|
||||
|
||||
//
|
||||
// process associated text
|
||||
//
|
||||
if (HasAssociatedText())
|
||||
{
|
||||
int extra;
|
||||
|
||||
int textLength = GetAssociatedTextLength();
|
||||
if (textLength < ((1 << 16) - (1 << 8)))
|
||||
{
|
||||
cMac.Update((byte)(textLength >> 8));
|
||||
cMac.Update((byte)textLength);
|
||||
|
||||
extra = 2;
|
||||
}
|
||||
else // can't go any higher than 2^32
|
||||
{
|
||||
cMac.Update((byte)0xff);
|
||||
cMac.Update((byte)0xfe);
|
||||
cMac.Update((byte)(textLength >> 24));
|
||||
cMac.Update((byte)(textLength >> 16));
|
||||
cMac.Update((byte)(textLength >> 8));
|
||||
cMac.Update((byte)textLength);
|
||||
|
||||
extra = 6;
|
||||
}
|
||||
|
||||
if (initialAssociatedText != null)
|
||||
{
|
||||
cMac.BlockUpdate(initialAssociatedText, 0, initialAssociatedText.Length);
|
||||
}
|
||||
if (associatedText.Length > 0)
|
||||
{
|
||||
byte[] input = associatedText.GetBuffer();
|
||||
int len = Convert.ToInt32(associatedText.Length);
|
||||
|
||||
cMac.BlockUpdate(input, 0, len);
|
||||
}
|
||||
|
||||
extra = (extra + textLength) % 16;
|
||||
if (extra != 0)
|
||||
{
|
||||
for (int i = extra; i < 16; ++i)
|
||||
{
|
||||
cMac.Update((byte)0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// add the text
|
||||
//
|
||||
cMac.BlockUpdate(data, dataOff, dataLen);
|
||||
|
||||
return cMac.DoFinal(macBlock, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
private int CalculateMac(ReadOnlySpan<byte> data, Span<byte> macBlock)
|
||||
{
|
||||
IMac cMac = new CbcBlockCipherMac(cipher, macSize * 8);
|
||||
|
||||
cMac.Init(keyParam);
|
||||
|
||||
//
|
||||
// build b0
|
||||
//
|
||||
byte[] b0 = new byte[16];
|
||||
|
||||
if (HasAssociatedText())
|
||||
{
|
||||
b0[0] |= 0x40;
|
||||
}
|
||||
|
||||
b0[0] |= (byte)((((cMac.GetMacSize() - 2) / 2) & 0x7) << 3);
|
||||
|
||||
b0[0] |= (byte)(((15 - nonce.Length) - 1) & 0x7);
|
||||
|
||||
Array.Copy(nonce, 0, b0, 1, nonce.Length);
|
||||
|
||||
int q = data.Length;
|
||||
int count = 1;
|
||||
while (q > 0)
|
||||
{
|
||||
b0[b0.Length - count] = (byte)(q & 0xff);
|
||||
q >>= 8;
|
||||
count++;
|
||||
}
|
||||
|
||||
cMac.BlockUpdate(b0, 0, b0.Length);
|
||||
|
||||
//
|
||||
// process associated text
|
||||
//
|
||||
if (HasAssociatedText())
|
||||
{
|
||||
int extra;
|
||||
|
||||
int textLength = GetAssociatedTextLength();
|
||||
if (textLength < ((1 << 16) - (1 << 8)))
|
||||
{
|
||||
cMac.Update((byte)(textLength >> 8));
|
||||
cMac.Update((byte)textLength);
|
||||
|
||||
extra = 2;
|
||||
}
|
||||
else // can't go any higher than 2^32
|
||||
{
|
||||
cMac.Update((byte)0xff);
|
||||
cMac.Update((byte)0xfe);
|
||||
cMac.Update((byte)(textLength >> 24));
|
||||
cMac.Update((byte)(textLength >> 16));
|
||||
cMac.Update((byte)(textLength >> 8));
|
||||
cMac.Update((byte)textLength);
|
||||
|
||||
extra = 6;
|
||||
}
|
||||
|
||||
if (initialAssociatedText != null)
|
||||
{
|
||||
cMac.BlockUpdate(initialAssociatedText, 0, initialAssociatedText.Length);
|
||||
}
|
||||
if (associatedText.Length > 0)
|
||||
{
|
||||
byte[] input = associatedText.GetBuffer();
|
||||
int len = Convert.ToInt32(associatedText.Length);
|
||||
|
||||
cMac.BlockUpdate(input, 0, len);
|
||||
}
|
||||
|
||||
extra = (extra + textLength) % 16;
|
||||
if (extra != 0)
|
||||
{
|
||||
for (int i = extra; i < 16; ++i)
|
||||
{
|
||||
cMac.Update((byte)0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// add the text
|
||||
//
|
||||
cMac.BlockUpdate(data);
|
||||
|
||||
return cMac.DoFinal(macBlock);
|
||||
}
|
||||
#endif
|
||||
|
||||
private int GetMacSize(bool forEncryption, int requestedMacBits)
|
||||
{
|
||||
if (forEncryption && (requestedMacBits < 32 || requestedMacBits > 128 || 0 != (requestedMacBits & 15)))
|
||||
throw new ArgumentException("tag length in octets must be one of {4,6,8,10,12,14,16}");
|
||||
|
||||
return requestedMacBits >> 3;
|
||||
}
|
||||
|
||||
private int GetAssociatedTextLength()
|
||||
{
|
||||
return Convert.ToInt32(associatedText.Length) +
|
||||
(initialAssociatedText == null ? 0 : initialAssociatedText.Length);
|
||||
}
|
||||
|
||||
private bool HasAssociatedText()
|
||||
{
|
||||
return GetAssociatedTextLength() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,531 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Engines;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of Daniel J. Bernstein's ChaCha stream cipher.
|
||||
/// </summary>
|
||||
|
||||
|
||||
|
||||
[BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
|
||||
public sealed class FastChaCha7539Engine
|
||||
: FastSalsa20Engine
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a 20 rounds ChaCha engine.
|
||||
/// </summary>
|
||||
public FastChaCha7539Engine()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public override string AlgorithmName
|
||||
{
|
||||
get { return "ChaCha7539"; }
|
||||
}
|
||||
|
||||
protected override int NonceSize
|
||||
{
|
||||
get { return 12; }
|
||||
}
|
||||
|
||||
protected override void AdvanceCounter()
|
||||
{
|
||||
if (++engineState[12] == 0)
|
||||
throw new InvalidOperationException("attempt to increase counter past 2^32.");
|
||||
}
|
||||
|
||||
protected override void ResetCounter()
|
||||
{
|
||||
engineState[12] = 0;
|
||||
}
|
||||
|
||||
protected override void SetKey(byte[] keyBytes, byte[] ivBytes)
|
||||
{
|
||||
if (keyBytes != null)
|
||||
{
|
||||
if (keyBytes.Length != 32)
|
||||
throw new ArgumentException(AlgorithmName + " requires 256 bit key");
|
||||
|
||||
PackTauOrSigma(keyBytes.Length, engineState, 0);
|
||||
|
||||
// Key
|
||||
Pack.LE_To_UInt32(keyBytes, 0, engineState, 4, 8);
|
||||
}
|
||||
|
||||
// IV
|
||||
Pack.LE_To_UInt32(ivBytes, 0, engineState, 13, 3);
|
||||
}
|
||||
|
||||
protected override void GenerateKeyStream(byte[] output)
|
||||
{
|
||||
FastChaChaEngineHelper.ChachaCore(rounds, engineState, output);
|
||||
}
|
||||
|
||||
internal void DoFinal(byte[] inBuf, int inOff, int inLen, byte[] outBuf, int outOff)
|
||||
{
|
||||
if (!initialised)
|
||||
throw new InvalidOperationException(AlgorithmName + " not initialised");
|
||||
if (index != 0)
|
||||
throw new InvalidOperationException(AlgorithmName + " not in block-aligned state");
|
||||
|
||||
Check.DataLength(inBuf, inOff, inLen, "input buffer too short");
|
||||
Check.OutputLength(outBuf, outOff, inLen, "output buffer too short");
|
||||
|
||||
while (inLen >= 128)
|
||||
{
|
||||
#if BESTHTTP_WITH_BURST
|
||||
FastChaCha7539EngineHelper.ProcessBlocks2(inBuf.AsSpan(inOff), outBuf.AsSpan(outOff), engineState, rounds, keyStream);
|
||||
#elif NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
//ProcessBlocks2(inBuf.AsSpan(inOff), outBuf.AsSpan(outOff));
|
||||
|
||||
var input = inBuf.AsSpan(inOff);
|
||||
var output = outBuf.AsSpan(outOff);
|
||||
ImplProcessBlock(inBuf.AsSpan(inOff), outBuf.AsSpan(outOff));
|
||||
ImplProcessBlock(input[64..], output[64..]);
|
||||
#else
|
||||
ProcessBlocks2(inBuf, inOff, outBuf, outOff);
|
||||
#endif
|
||||
inOff += 128;
|
||||
inLen -= 128;
|
||||
outOff += 128;
|
||||
}
|
||||
|
||||
if (inLen >= 64)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
ImplProcessBlock(inBuf.AsSpan(inOff), outBuf.AsSpan(outOff));
|
||||
#else
|
||||
ImplProcessBlock(inBuf, inOff, outBuf, outOff);
|
||||
#endif
|
||||
inOff += 64;
|
||||
inLen -= 64;
|
||||
outOff += 64;
|
||||
}
|
||||
|
||||
if (inLen > 0)
|
||||
{
|
||||
GenerateKeyStream(keyStream);
|
||||
//AdvanceCounter();
|
||||
if (++engineState[12] == 0)
|
||||
throw new InvalidOperationException("attempt to increase counter past 2^32.");
|
||||
|
||||
for (int i = 0; i < inLen; ++i)
|
||||
{
|
||||
outBuf[outOff + i] = (byte)(inBuf[i + inOff] ^ keyStream[i]);
|
||||
}
|
||||
}
|
||||
|
||||
engineState[12] = 0;
|
||||
|
||||
// TODO Prevent re-use if encrypting
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
internal void ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
|
||||
{
|
||||
if (!initialised)
|
||||
throw new InvalidOperationException(AlgorithmName + " not initialised");
|
||||
if (LimitExceeded(64U))
|
||||
throw new MaxBytesExceededException("2^38 byte limit per IV would be exceeded; Change IV");
|
||||
|
||||
UnityEngine.Debug.Assert(index == 0);
|
||||
|
||||
ImplProcessBlock(input, output);
|
||||
}
|
||||
|
||||
internal void ProcessBlocks2(ReadOnlySpan<byte> input, Span<byte> output)
|
||||
{
|
||||
if (!initialised)
|
||||
throw new InvalidOperationException(AlgorithmName + " not initialised");
|
||||
if (LimitExceeded(128U))
|
||||
throw new MaxBytesExceededException("2^38 byte limit per IV would be exceeded; Change IV");
|
||||
|
||||
UnityEngine.Debug.Assert(index == 0);
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
if (Avx2.IsSupported)
|
||||
{
|
||||
ImplProcessBlocks2_X86_Avx2(rounds, engineState, input, output);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Sse2.IsSupported)
|
||||
{
|
||||
ImplProcessBlocks2_X86_Sse2(rounds, engineState, input, output);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
ImplProcessBlock(input, output);
|
||||
ImplProcessBlock(input[64..], output[64..]);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void ImplProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
|
||||
{
|
||||
FastChaChaEngineHelper.ChachaCore(rounds, engineState, keyStream);
|
||||
|
||||
//AdvanceCounter();
|
||||
if (++engineState[12] == 0)
|
||||
throw new InvalidOperationException("attempt to increase counter past 2^32.");
|
||||
|
||||
FastChaChaEngineHelper.ImplProcessBlock(input, output, keyStream);
|
||||
}
|
||||
#else
|
||||
internal void ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
|
||||
{
|
||||
if (!initialised)
|
||||
throw new InvalidOperationException(AlgorithmName + " not initialised");
|
||||
if (LimitExceeded(64U))
|
||||
throw new MaxBytesExceededException("2^38 byte limit per IV would be exceeded; Change IV");
|
||||
|
||||
UnityEngine.Debug.Assert(index == 0);
|
||||
|
||||
ImplProcessBlock(inBytes, inOff, outBytes, outOff);
|
||||
}
|
||||
|
||||
internal void ProcessBlocks2(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
|
||||
{
|
||||
if (!initialised)
|
||||
throw new InvalidOperationException(AlgorithmName + " not initialised");
|
||||
if (LimitExceeded(128U))
|
||||
throw new MaxBytesExceededException("2^38 byte limit per IV would be exceeded; Change IV");
|
||||
|
||||
UnityEngine.Debug.Assert(index == 0);
|
||||
|
||||
{
|
||||
ImplProcessBlock(inBytes, inOff, outBytes, outOff);
|
||||
ImplProcessBlock(inBytes, inOff + 64, outBytes, outOff + 64);
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER || UNITY_2021_2_OR_NEWER
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
internal void ImplProcessBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
|
||||
{
|
||||
ChaChaEngine.ChachaCore(rounds, engineState, keyStream);
|
||||
AdvanceCounter();
|
||||
|
||||
for (int i = 0; i < 64; ++i)
|
||||
{
|
||||
outBuf[outOff + i] = (byte)(keyStream[i] ^ inBuf[inOff + i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void ImplProcessBlocks2_X86_Avx2(int rounds, uint[] state, ReadOnlySpan<byte> input,
|
||||
Span<byte> output)
|
||||
{
|
||||
if (!Avx2.IsSupported)
|
||||
throw new PlatformNotSupportedException();
|
||||
|
||||
Debug.Assert(rounds % 2 == 0);
|
||||
Debug.Assert(state.Length >= 16);
|
||||
Debug.Assert(input.Length >= 128);
|
||||
Debug.Assert(output.Length >= 128);
|
||||
|
||||
var t0 = Load128_UInt32(state.AsSpan());
|
||||
var t1 = Load128_UInt32(state.AsSpan(4));
|
||||
var t2 = Load128_UInt32(state.AsSpan(8));
|
||||
var t3 = Load128_UInt32(state.AsSpan(12));
|
||||
++state[12];
|
||||
var t4 = Load128_UInt32(state.AsSpan(12));
|
||||
++state[12];
|
||||
|
||||
var x0 = Vector256.Create(t0, t0);
|
||||
var x1 = Vector256.Create(t1, t1);
|
||||
var x2 = Vector256.Create(t2, t2);
|
||||
var x3 = Vector256.Create(t3, t4);
|
||||
|
||||
var v0 = x0;
|
||||
var v1 = x1;
|
||||
var v2 = x2;
|
||||
var v3 = x3;
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
v0 = Avx2.Add(v0, v1);
|
||||
v3 = Avx2.Xor(v3, v0);
|
||||
v3 = Avx2.Xor(Avx2.ShiftLeftLogical(v3, 16), Avx2.ShiftRightLogical(v3, 16));
|
||||
v2 = Avx2.Add(v2, v3);
|
||||
v1 = Avx2.Xor(v1, v2);
|
||||
v1 = Avx2.Xor(Avx2.ShiftLeftLogical(v1, 12), Avx2.ShiftRightLogical(v1, 20));
|
||||
v0 = Avx2.Add(v0, v1);
|
||||
v3 = Avx2.Xor(v3, v0);
|
||||
v3 = Avx2.Xor(Avx2.ShiftLeftLogical(v3, 8), Avx2.ShiftRightLogical(v3, 24));
|
||||
v2 = Avx2.Add(v2, v3);
|
||||
v1 = Avx2.Xor(v1, v2);
|
||||
v1 = Avx2.Xor(Avx2.ShiftLeftLogical(v1, 7), Avx2.ShiftRightLogical(v1, 25));
|
||||
|
||||
v1 = Avx2.Shuffle(v1, 0x39);
|
||||
v2 = Avx2.Shuffle(v2, 0x4E);
|
||||
v3 = Avx2.Shuffle(v3, 0x93);
|
||||
|
||||
v0 = Avx2.Add(v0, v1);
|
||||
v3 = Avx2.Xor(v3, v0);
|
||||
v3 = Avx2.Xor(Avx2.ShiftLeftLogical(v3, 16), Avx2.ShiftRightLogical(v3, 16));
|
||||
v2 = Avx2.Add(v2, v3);
|
||||
v1 = Avx2.Xor(v1, v2);
|
||||
v1 = Avx2.Xor(Avx2.ShiftLeftLogical(v1, 12), Avx2.ShiftRightLogical(v1, 20));
|
||||
v0 = Avx2.Add(v0, v1);
|
||||
v3 = Avx2.Xor(v3, v0);
|
||||
v3 = Avx2.Xor(Avx2.ShiftLeftLogical(v3, 8), Avx2.ShiftRightLogical(v3, 24));
|
||||
v2 = Avx2.Add(v2, v3);
|
||||
v1 = Avx2.Xor(v1, v2);
|
||||
v1 = Avx2.Xor(Avx2.ShiftLeftLogical(v1, 7), Avx2.ShiftRightLogical(v1, 25));
|
||||
|
||||
v1 = Avx2.Shuffle(v1, 0x93);
|
||||
v2 = Avx2.Shuffle(v2, 0x4E);
|
||||
v3 = Avx2.Shuffle(v3, 0x39);
|
||||
}
|
||||
|
||||
v0 = Avx2.Add(v0, x0);
|
||||
v1 = Avx2.Add(v1, x1);
|
||||
v2 = Avx2.Add(v2, x2);
|
||||
v3 = Avx2.Add(v3, x3);
|
||||
|
||||
var n0 = Avx2.Permute2x128(v0, v1, 0x20).AsByte();
|
||||
var n1 = Avx2.Permute2x128(v2, v3, 0x20).AsByte();
|
||||
var n2 = Avx2.Permute2x128(v0, v1, 0x31).AsByte();
|
||||
var n3 = Avx2.Permute2x128(v2, v3, 0x31).AsByte();
|
||||
|
||||
n0 = Avx2.Xor(n0, Load256_Byte(input));
|
||||
n1 = Avx2.Xor(n1, Load256_Byte(input[0x20..]));
|
||||
n2 = Avx2.Xor(n2, Load256_Byte(input[0x40..]));
|
||||
n3 = Avx2.Xor(n3, Load256_Byte(input[0x60..]));
|
||||
|
||||
Store256_Byte(n0, output);
|
||||
Store256_Byte(n1, output[0x20..]);
|
||||
Store256_Byte(n2, output[0x40..]);
|
||||
Store256_Byte(n3, output[0x60..]);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void ImplProcessBlocks2_X86_Sse2(int rounds, uint[] state, ReadOnlySpan<byte> input,
|
||||
Span<byte> output)
|
||||
{
|
||||
if (!Sse2.IsSupported)
|
||||
throw new PlatformNotSupportedException();
|
||||
|
||||
Debug.Assert(rounds % 2 == 0);
|
||||
Debug.Assert(state.Length >= 16);
|
||||
Debug.Assert(input.Length >= 128);
|
||||
Debug.Assert(output.Length >= 128);
|
||||
|
||||
var x0 = Load128_UInt32(state.AsSpan());
|
||||
var x1 = Load128_UInt32(state.AsSpan(4));
|
||||
var x2 = Load128_UInt32(state.AsSpan(8));
|
||||
var x3 = Load128_UInt32(state.AsSpan(12));
|
||||
++state[12];
|
||||
|
||||
var v0 = x0;
|
||||
var v1 = x1;
|
||||
var v2 = x2;
|
||||
var v3 = x3;
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
v0 = Sse2.Add(v0, v1);
|
||||
v3 = Sse2.Xor(v3, v0);
|
||||
v3 = Sse2.Xor(Sse2.ShiftLeftLogical(v3, 16), Sse2.ShiftRightLogical(v3, 16));
|
||||
v2 = Sse2.Add(v2, v3);
|
||||
v1 = Sse2.Xor(v1, v2);
|
||||
v1 = Sse2.Xor(Sse2.ShiftLeftLogical(v1, 12), Sse2.ShiftRightLogical(v1, 20));
|
||||
v0 = Sse2.Add(v0, v1);
|
||||
v3 = Sse2.Xor(v3, v0);
|
||||
v3 = Sse2.Xor(Sse2.ShiftLeftLogical(v3, 8), Sse2.ShiftRightLogical(v3, 24));
|
||||
v2 = Sse2.Add(v2, v3);
|
||||
v1 = Sse2.Xor(v1, v2);
|
||||
v1 = Sse2.Xor(Sse2.ShiftLeftLogical(v1, 7), Sse2.ShiftRightLogical(v1, 25));
|
||||
|
||||
v1 = Sse2.Shuffle(v1, 0x39);
|
||||
v2 = Sse2.Shuffle(v2, 0x4E);
|
||||
v3 = Sse2.Shuffle(v3, 0x93);
|
||||
|
||||
v0 = Sse2.Add(v0, v1);
|
||||
v3 = Sse2.Xor(v3, v0);
|
||||
v3 = Sse2.Xor(Sse2.ShiftLeftLogical(v3, 16), Sse2.ShiftRightLogical(v3, 16));
|
||||
v2 = Sse2.Add(v2, v3);
|
||||
v1 = Sse2.Xor(v1, v2);
|
||||
v1 = Sse2.Xor(Sse2.ShiftLeftLogical(v1, 12), Sse2.ShiftRightLogical(v1, 20));
|
||||
v0 = Sse2.Add(v0, v1);
|
||||
v3 = Sse2.Xor(v3, v0);
|
||||
v3 = Sse2.Xor(Sse2.ShiftLeftLogical(v3, 8), Sse2.ShiftRightLogical(v3, 24));
|
||||
v2 = Sse2.Add(v2, v3);
|
||||
v1 = Sse2.Xor(v1, v2);
|
||||
v1 = Sse2.Xor(Sse2.ShiftLeftLogical(v1, 7), Sse2.ShiftRightLogical(v1, 25));
|
||||
|
||||
v1 = Sse2.Shuffle(v1, 0x93);
|
||||
v2 = Sse2.Shuffle(v2, 0x4E);
|
||||
v3 = Sse2.Shuffle(v3, 0x39);
|
||||
}
|
||||
|
||||
v0 = Sse2.Add(v0, x0);
|
||||
v1 = Sse2.Add(v1, x1);
|
||||
v2 = Sse2.Add(v2, x2);
|
||||
v3 = Sse2.Add(v3, x3);
|
||||
|
||||
var n0 = Load128_Byte(input);
|
||||
var n1 = Load128_Byte(input[0x10..]);
|
||||
var n2 = Load128_Byte(input[0x20..]);
|
||||
var n3 = Load128_Byte(input[0x30..]);
|
||||
|
||||
n0 = Sse2.Xor(n0, v0.AsByte());
|
||||
n1 = Sse2.Xor(n1, v1.AsByte());
|
||||
n2 = Sse2.Xor(n2, v2.AsByte());
|
||||
n3 = Sse2.Xor(n3, v3.AsByte());
|
||||
|
||||
Store128_Byte(n0, output);
|
||||
Store128_Byte(n1, output[0x10..]);
|
||||
Store128_Byte(n2, output[0x20..]);
|
||||
Store128_Byte(n3, output[0x30..]);
|
||||
|
||||
x3 = Load128_UInt32(state.AsSpan(12));
|
||||
++state[12];
|
||||
|
||||
v0 = x0;
|
||||
v1 = x1;
|
||||
v2 = x2;
|
||||
v3 = x3;
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
v0 = Sse2.Add(v0, v1);
|
||||
v3 = Sse2.Xor(v3, v0);
|
||||
v3 = Sse2.Xor(Sse2.ShiftLeftLogical(v3, 16), Sse2.ShiftRightLogical(v3, 16));
|
||||
v2 = Sse2.Add(v2, v3);
|
||||
v1 = Sse2.Xor(v1, v2);
|
||||
v1 = Sse2.Xor(Sse2.ShiftLeftLogical(v1, 12), Sse2.ShiftRightLogical(v1, 20));
|
||||
v0 = Sse2.Add(v0, v1);
|
||||
v3 = Sse2.Xor(v3, v0);
|
||||
v3 = Sse2.Xor(Sse2.ShiftLeftLogical(v3, 8), Sse2.ShiftRightLogical(v3, 24));
|
||||
v2 = Sse2.Add(v2, v3);
|
||||
v1 = Sse2.Xor(v1, v2);
|
||||
v1 = Sse2.Xor(Sse2.ShiftLeftLogical(v1, 7), Sse2.ShiftRightLogical(v1, 25));
|
||||
|
||||
v1 = Sse2.Shuffle(v1, 0x39);
|
||||
v2 = Sse2.Shuffle(v2, 0x4E);
|
||||
v3 = Sse2.Shuffle(v3, 0x93);
|
||||
|
||||
v0 = Sse2.Add(v0, v1);
|
||||
v3 = Sse2.Xor(v3, v0);
|
||||
v3 = Sse2.Xor(Sse2.ShiftLeftLogical(v3, 16), Sse2.ShiftRightLogical(v3, 16));
|
||||
v2 = Sse2.Add(v2, v3);
|
||||
v1 = Sse2.Xor(v1, v2);
|
||||
v1 = Sse2.Xor(Sse2.ShiftLeftLogical(v1, 12), Sse2.ShiftRightLogical(v1, 20));
|
||||
v0 = Sse2.Add(v0, v1);
|
||||
v3 = Sse2.Xor(v3, v0);
|
||||
v3 = Sse2.Xor(Sse2.ShiftLeftLogical(v3, 8), Sse2.ShiftRightLogical(v3, 24));
|
||||
v2 = Sse2.Add(v2, v3);
|
||||
v1 = Sse2.Xor(v1, v2);
|
||||
v1 = Sse2.Xor(Sse2.ShiftLeftLogical(v1, 7), Sse2.ShiftRightLogical(v1, 25));
|
||||
|
||||
v1 = Sse2.Shuffle(v1, 0x93);
|
||||
v2 = Sse2.Shuffle(v2, 0x4E);
|
||||
v3 = Sse2.Shuffle(v3, 0x39);
|
||||
}
|
||||
|
||||
v0 = Sse2.Add(v0, x0);
|
||||
v1 = Sse2.Add(v1, x1);
|
||||
v2 = Sse2.Add(v2, x2);
|
||||
v3 = Sse2.Add(v3, x3);
|
||||
|
||||
n0 = Load128_Byte(input[0x40..]);
|
||||
n1 = Load128_Byte(input[0x50..]);
|
||||
n2 = Load128_Byte(input[0x60..]);
|
||||
n3 = Load128_Byte(input[0x70..]);
|
||||
|
||||
n0 = Sse2.Xor(n0, v0.AsByte());
|
||||
n1 = Sse2.Xor(n1, v1.AsByte());
|
||||
n2 = Sse2.Xor(n2, v2.AsByte());
|
||||
n3 = Sse2.Xor(n3, v3.AsByte());
|
||||
|
||||
Store128_Byte(n0, output[0x40..]);
|
||||
Store128_Byte(n1, output[0x50..]);
|
||||
Store128_Byte(n2, output[0x60..]);
|
||||
Store128_Byte(n3, output[0x70..]);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector128<byte> Load128_Byte(ReadOnlySpan<byte> t)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
|
||||
return MemoryMarshal.Read<Vector128<byte>>(t);
|
||||
|
||||
return Vector128.Create(
|
||||
BinaryPrimitives.ReadUInt64LittleEndian(t[..8]),
|
||||
BinaryPrimitives.ReadUInt64LittleEndian(t[8..])
|
||||
).AsByte();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector128<uint> Load128_UInt32(ReadOnlySpan<uint> t)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<uint>>() == 16)
|
||||
return MemoryMarshal.Read<Vector128<uint>>(MemoryMarshal.AsBytes(t));
|
||||
|
||||
return Vector128.Create(t[0], t[1], t[2], t[3]);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector256<byte> Load256_Byte(ReadOnlySpan<byte> t)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector256<byte>>() == 32)
|
||||
return MemoryMarshal.Read<Vector256<byte>>(t);
|
||||
|
||||
return Vector256.Create(
|
||||
BinaryPrimitives.ReadUInt64LittleEndian(t[ 0.. 8]),
|
||||
BinaryPrimitives.ReadUInt64LittleEndian(t[ 8..16]),
|
||||
BinaryPrimitives.ReadUInt64LittleEndian(t[16..24]),
|
||||
BinaryPrimitives.ReadUInt64LittleEndian(t[24..32])
|
||||
).AsByte();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Store128_Byte(Vector128<byte> s, Span<byte> t)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
|
||||
{
|
||||
MemoryMarshal.Write(t, ref s);
|
||||
return;
|
||||
}
|
||||
|
||||
var u = s.AsUInt64();
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(t[..8], u.GetElement(0));
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(t[8..], u.GetElement(1));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Store256_Byte(Vector256<byte> s, Span<byte> t)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector256<byte>>() == 32)
|
||||
{
|
||||
MemoryMarshal.Write(t, ref s);
|
||||
return;
|
||||
}
|
||||
|
||||
var u = s.AsUInt64();
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(t[ 0.. 8], u.GetElement(0));
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(t[ 8..16], u.GetElement(1));
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(t[16..24], u.GetElement(2));
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(t[24..32], u.GetElement(3));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,484 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) && BESTHTTP_WITH_BURST
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Unity.Burst;
|
||||
|
||||
using Unity.Burst.Intrinsics;
|
||||
|
||||
using static Unity.Burst.Intrinsics.X86;
|
||||
using static Unity.Burst.Intrinsics.Arm;
|
||||
|
||||
// https://github.com/sschoener/burst-simd-exercises/blob/main/Assets/Examples/2-sum-small-numbers-sse3/SumSmallNumbers_SSE3.cs
|
||||
// https://github.com/jratcliff63367/sse2neon/blob/master/SSE2NEON.h#L789
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
[BurstCompile]
|
||||
public unsafe static class FastChaCha7539EngineHelper
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ProcessBlocks2(ReadOnlySpan<byte> input, Span<byte> output, uint[] state, int rounds, byte[] keyStream)
|
||||
{
|
||||
fixed (byte* pinput = input)
|
||||
fixed (byte* poutput = output)
|
||||
fixed (uint* pstate = state)
|
||||
fixed(byte* pkeyStream = keyStream)
|
||||
ProcessBlocks2Impl(pinput, input.Length, poutput, output.Length, pstate, state.Length, rounds, pkeyStream);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[BurstCompile(CompileSynchronously = true)]
|
||||
private static void ProcessBlocks2Impl([NoAlias] byte* input, int inputLen, [NoAlias] byte* output, int outLen, [NoAlias] uint* state, int stateLen, int rounds, [NoAlias] byte* keyStream)
|
||||
{
|
||||
if (Avx2.IsAvx2Supported)
|
||||
{
|
||||
var t0 = new v128(state[0], state[1], state[2], state[3]); //Load128_UInt32(state.AsSpan());
|
||||
var t1 = new v128(state[4], state[5], state[6], state[7]); //Load128_UInt32(state.AsSpan(4));
|
||||
var t2 = new v128(state[8], state[9], state[10], state[11]); //Load128_UInt32(state.AsSpan(8));
|
||||
var t3 = new v128(state[12], state[13], state[14], state[15]); //Load128_UInt32(state.AsSpan(12));
|
||||
++state[12];
|
||||
var t4 = new v128(state[12], state[13], state[14], state[15]); //Load128_UInt32(state.AsSpan(12));
|
||||
++state[12];
|
||||
|
||||
var x0 = new v256(t0, t0); //Vector256.Create(t0, t0);
|
||||
var x1 = new v256(t1, t1); //Vector256.Create(t1, t1);
|
||||
var x2 = new v256(t2, t2); //Vector256.Create(t2, t2);
|
||||
var x3 = new v256(t3, t4); //Vector256.Create(t3, t4);
|
||||
|
||||
var v0 = x0;
|
||||
var v1 = x1;
|
||||
var v2 = x2;
|
||||
var v3 = x3;
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
v0 = Avx2.mm256_add_epi32(v0, v1);
|
||||
v3 = Avx2.mm256_xor_si256(v3, v0);
|
||||
v3 = Avx2.mm256_xor_si256(Avx2.mm256_slli_epi32(v3, 16), Avx2.mm256_srli_epi32(v3, 16));
|
||||
v2 = Avx2.mm256_add_epi32(v2, v3);
|
||||
v1 = Avx2.mm256_xor_si256(v1, v2);
|
||||
v1 = Avx2.mm256_xor_si256(Avx2.mm256_slli_epi32(v1, 12), Avx2.mm256_srli_epi32(v1, 20));
|
||||
v0 = Avx2.mm256_add_epi32(v0, v1);
|
||||
v3 = Avx2.mm256_xor_si256(v3, v0);
|
||||
v3 = Avx2.mm256_xor_si256(Avx2.mm256_slli_epi32(v3, 8), Avx2.mm256_srli_epi32(v3, 24));
|
||||
v2 = Avx2.mm256_add_epi32(v2, v3);
|
||||
v1 = Avx2.mm256_xor_si256(v1, v2);
|
||||
v1 = Avx2.mm256_xor_si256(Avx2.mm256_slli_epi32(v1, 7), Avx2.mm256_srli_epi32(v1, 25));
|
||||
|
||||
v1 = Avx2.mm256_shuffle_epi32(v1, 0x39);
|
||||
v2 = Avx2.mm256_shuffle_epi32(v2, 0x4E);
|
||||
v3 = Avx2.mm256_shuffle_epi32(v3, 0x93);
|
||||
|
||||
v0 = Avx2.mm256_add_epi32(v0, v1);
|
||||
v3 = Avx2.mm256_xor_si256(v3, v0);
|
||||
v3 = Avx2.mm256_xor_si256(Avx2.mm256_slli_epi32(v3, 16), Avx2.mm256_srli_epi32(v3, 16));
|
||||
v2 = Avx2.mm256_add_epi32(v2, v3);
|
||||
v1 = Avx2.mm256_xor_si256(v1, v2);
|
||||
v1 = Avx2.mm256_xor_si256(Avx2.mm256_slli_epi32(v1, 12), Avx2.mm256_srli_epi32(v1, 20));
|
||||
v0 = Avx2.mm256_add_epi32(v0, v1);
|
||||
v3 = Avx2.mm256_xor_si256(v3, v0);
|
||||
v3 = Avx2.mm256_xor_si256(Avx2.mm256_slli_epi32(v3, 8), Avx2.mm256_srli_epi32(v3, 24));
|
||||
v2 = Avx2.mm256_add_epi32(v2, v3);
|
||||
v1 = Avx2.mm256_xor_si256(v1, v2);
|
||||
v1 = Avx2.mm256_xor_si256(Avx2.mm256_slli_epi32(v1, 7), Avx2.mm256_srli_epi32(v1, 25));
|
||||
|
||||
v1 = Avx2.mm256_shuffle_epi32(v1, 0x93);
|
||||
v2 = Avx2.mm256_shuffle_epi32(v2, 0x4E);
|
||||
v3 = Avx2.mm256_shuffle_epi32(v3, 0x39);
|
||||
}
|
||||
|
||||
v0 = Avx2.mm256_add_epi32(v0, x0);
|
||||
v1 = Avx2.mm256_add_epi32(v1, x1);
|
||||
v2 = Avx2.mm256_add_epi32(v2, x2);
|
||||
v3 = Avx2.mm256_add_epi32(v3, x3);
|
||||
|
||||
var n0 = Avx2.mm256_permute2x128_si256(v0, v1, 0x20);
|
||||
var n1 = Avx2.mm256_permute2x128_si256(v2, v3, 0x20);
|
||||
var n2 = Avx2.mm256_permute2x128_si256(v0, v1, 0x31);
|
||||
var n3 = Avx2.mm256_permute2x128_si256(v2, v3, 0x31);
|
||||
|
||||
ulong* uInput = (ulong*)input;
|
||||
n0 = Avx2.mm256_xor_si256(n0, new v256(uInput[0], uInput[1], uInput[2], uInput[3])); // Load256_Byte(input)
|
||||
n1 = Avx2.mm256_xor_si256(n1, new v256(uInput[4], uInput[5], uInput[6], uInput[7])); // Load256_Byte(input[0x20..])
|
||||
n2 = Avx2.mm256_xor_si256(n2, new v256(uInput[8], uInput[9], uInput[10], uInput[11])); // Load256_Byte(input[0x40..])
|
||||
n3 = Avx2.mm256_xor_si256(n3, new v256(uInput[12], uInput[13], uInput[14], uInput[15])); // Load256_Byte(input[0x60..])
|
||||
|
||||
ulong* uOutput = (ulong*)output;
|
||||
uOutput[0] = n0.ULong0; uOutput[1] = n0.ULong1; uOutput[2] = n0.ULong2; uOutput[3] = n0.ULong3; //Store256_Byte(n0, output);
|
||||
uOutput[4] = n1.ULong0; uOutput[5] = n1.ULong1; uOutput[6] = n1.ULong2; uOutput[7] = n1.ULong3; //Store256_Byte(n1, output[0x20..]);
|
||||
uOutput[8] = n2.ULong0; uOutput[9] = n2.ULong1; uOutput[10] = n2.ULong2; uOutput[11] = n2.ULong3; //Store256_Byte(n2, output[0x40..]);
|
||||
uOutput[12] = n3.ULong0; uOutput[13] = n3.ULong1; uOutput[14] = n3.ULong2; uOutput[15] = n3.ULong3; //Store256_Byte(n3, output[0x60..]);
|
||||
}
|
||||
else if (Sse2.IsSse2Supported)
|
||||
{
|
||||
var x0 = Sse2.loadu_si128(state); //new v128(state[0], state[1], state[2], state[3]); //Load128_UInt32(state.AsSpan());
|
||||
var x1 = Sse2.loadu_si128(state + 4); //new v128(state[4], state[5], state[6], state[7]); //Load128_UInt32(state.AsSpan(4));
|
||||
var x2 = Sse2.loadu_si128(state + 8); //new v128(state[8], state[9], state[10], state[11]); //Load128_UInt32(state.AsSpan(8));
|
||||
var x3 = Sse2.loadu_si128(state + 12); //new v128(state[12], state[13], state[14], state[15]); //Load128_UInt32(state.AsSpan(12));
|
||||
++state[12];
|
||||
|
||||
var v0 = x0;
|
||||
var v1 = x1;
|
||||
var v2 = x2;
|
||||
var v3 = x3;
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
v0 = Sse2.add_epi32(v0, v1);
|
||||
v3 = Sse2.xor_si128(v3, v0);
|
||||
v3 = Sse2.xor_si128(Sse2.slli_epi32(v3, 16), Sse2.srli_epi32(v3, 16));
|
||||
v2 = Sse2.add_epi32(v2, v3);
|
||||
v1 = Sse2.xor_si128(v1, v2);
|
||||
v1 = Sse2.xor_si128(Sse2.slli_epi32(v1, 12), Sse2.srli_epi32(v1, 20));
|
||||
v0 = Sse2.add_epi32(v0, v1);
|
||||
v3 = Sse2.xor_si128(v3, v0);
|
||||
v3 = Sse2.xor_si128(Sse2.slli_epi32(v3, 8), Sse2.srli_epi32(v3, 24));
|
||||
v2 = Sse2.add_epi32(v2, v3);
|
||||
v1 = Sse2.xor_si128(v1, v2);
|
||||
v1 = Sse2.xor_si128(Sse2.slli_epi32(v1, 7), Sse2.srli_epi32(v1, 25));
|
||||
|
||||
v1 = Sse2.shuffle_epi32(v1, 0x39);
|
||||
v2 = Sse2.shuffle_epi32(v2, 0x4E);
|
||||
v3 = Sse2.shuffle_epi32(v3, 0x93);
|
||||
|
||||
v0 = Sse2.add_epi32(v0, v1);
|
||||
v3 = Sse2.xor_si128(v3, v0);
|
||||
v3 = Sse2.xor_si128(Sse2.slli_epi32(v3, 16), Sse2.srli_epi32(v3, 16));
|
||||
v2 = Sse2.add_epi32(v2, v3);
|
||||
v1 = Sse2.xor_si128(v1, v2);
|
||||
v1 = Sse2.xor_si128(Sse2.slli_epi32(v1, 12), Sse2.srli_epi32(v1, 20));
|
||||
v0 = Sse2.add_epi32(v0, v1);
|
||||
v3 = Sse2.xor_si128(v3, v0);
|
||||
v3 = Sse2.xor_si128(Sse2.slli_epi32(v3, 8), Sse2.srli_epi32(v3, 24));
|
||||
v2 = Sse2.add_epi32(v2, v3);
|
||||
v1 = Sse2.xor_si128(v1, v2);
|
||||
v1 = Sse2.xor_si128(Sse2.slli_epi32(v1, 7), Sse2.srli_epi32(v1, 25));
|
||||
|
||||
v1 = Sse2.shuffle_epi32(v1, 0x93);
|
||||
v2 = Sse2.shuffle_epi32(v2, 0x4E);
|
||||
v3 = Sse2.shuffle_epi32(v3, 0x39);
|
||||
}
|
||||
|
||||
v0 = Sse2.add_epi32(v0, x0);
|
||||
v1 = Sse2.add_epi32(v1, x1);
|
||||
v2 = Sse2.add_epi32(v2, x2);
|
||||
v3 = Sse2.add_epi32(v3, x3);
|
||||
|
||||
var n0 = Sse2.loadu_si128(input + 0x00); //Load128_Byte(input);
|
||||
var n1 = Sse2.loadu_si128(input + 0x10); //Load128_Byte(input[0x10..]);
|
||||
var n2 = Sse2.loadu_si128(input + 0x20); //Load128_Byte(input[0x20..]);
|
||||
var n3 = Sse2.loadu_si128(input + 0x30); //Load128_Byte(input[0x30..]);
|
||||
|
||||
n0 = Sse2.xor_si128(n0, v0);
|
||||
n1 = Sse2.xor_si128(n1, v1);
|
||||
n2 = Sse2.xor_si128(n2, v2);
|
||||
n3 = Sse2.xor_si128(n3, v3);
|
||||
|
||||
Sse2.storeu_si128(output + 0x00, n0); //Store128_Byte(n0, output);
|
||||
Sse2.storeu_si128(output + 0x10, n1); //Store128_Byte(n1, output[0x10..]);
|
||||
Sse2.storeu_si128(output + 0x20, n2); //Store128_Byte(n2, output[0x20..]);
|
||||
Sse2.storeu_si128(output + 0x30, n3); //Store128_Byte(n3, output[0x30..]);
|
||||
|
||||
|
||||
x3 = Sse2.loadu_si128(state + 12); // Load128_UInt32(state.AsSpan(12));
|
||||
++state[12];
|
||||
|
||||
v0 = x0;
|
||||
v1 = x1;
|
||||
v2 = x2;
|
||||
v3 = x3;
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
v0 = Sse2.add_epi32(v0, v1);
|
||||
v3 = Sse2.xor_si128(v3, v0);
|
||||
v3 = Sse2.xor_si128(Sse2.slli_epi32(v3, 16), Sse2.srli_epi32(v3, 16));
|
||||
v2 = Sse2.add_epi32(v2, v3);
|
||||
v1 = Sse2.xor_si128(v1, v2);
|
||||
v1 = Sse2.xor_si128(Sse2.slli_epi32(v1, 12), Sse2.srli_epi32(v1, 20));
|
||||
v0 = Sse2.add_epi32(v0, v1);
|
||||
v3 = Sse2.xor_si128(v3, v0);
|
||||
v3 = Sse2.xor_si128(Sse2.slli_epi32(v3, 8), Sse2.srli_epi32(v3, 24));
|
||||
v2 = Sse2.add_epi32(v2, v3);
|
||||
v1 = Sse2.xor_si128(v1, v2);
|
||||
v1 = Sse2.xor_si128(Sse2.slli_epi32(v1, 7), Sse2.srli_epi32(v1, 25));
|
||||
|
||||
v1 = Sse2.shuffle_epi32(v1, 0x39);
|
||||
v2 = Sse2.shuffle_epi32(v2, 0x4E);
|
||||
v3 = Sse2.shuffle_epi32(v3, 0x93);
|
||||
|
||||
v0 = Sse2.add_epi32(v0, v1);
|
||||
v3 = Sse2.xor_si128(v3, v0);
|
||||
v3 = Sse2.xor_si128(Sse2.slli_epi32(v3, 16), Sse2.srli_epi32(v3, 16));
|
||||
v2 = Sse2.add_epi32(v2, v3);
|
||||
v1 = Sse2.xor_si128(v1, v2);
|
||||
v1 = Sse2.xor_si128(Sse2.slli_epi32(v1, 12), Sse2.srli_epi32(v1, 20));
|
||||
v0 = Sse2.add_epi32(v0, v1);
|
||||
v3 = Sse2.xor_si128(v3, v0);
|
||||
v3 = Sse2.xor_si128(Sse2.slli_epi32(v3, 8), Sse2.srli_epi32(v3, 24));
|
||||
v2 = Sse2.add_epi32(v2, v3);
|
||||
v1 = Sse2.xor_si128(v1, v2);
|
||||
v1 = Sse2.xor_si128(Sse2.slli_epi32(v1, 7), Sse2.srli_epi32(v1, 25));
|
||||
|
||||
v1 = Sse2.shuffle_epi32(v1, 0x93);
|
||||
v2 = Sse2.shuffle_epi32(v2, 0x4E);
|
||||
v3 = Sse2.shuffle_epi32(v3, 0x39);
|
||||
}
|
||||
|
||||
v0 = Sse2.add_epi32(v0, x0);
|
||||
v1 = Sse2.add_epi32(v1, x1);
|
||||
v2 = Sse2.add_epi32(v2, x2);
|
||||
v3 = Sse2.add_epi32(v3, x3);
|
||||
|
||||
n0 = Sse2.loadu_si128(input + 0x40); //Load128_Byte(input[0x40..]);
|
||||
n1 = Sse2.loadu_si128(input + 0x50); //Load128_Byte(input[0x50..]);
|
||||
n2 = Sse2.loadu_si128(input + 0x60); //Load128_Byte(input[0x60..]);
|
||||
n3 = Sse2.loadu_si128(input + 0x70); //Load128_Byte(input[0x70..]);
|
||||
|
||||
n0 = Sse2.xor_si128(n0, v0);
|
||||
n1 = Sse2.xor_si128(n1, v1);
|
||||
n2 = Sse2.xor_si128(n2, v2);
|
||||
n3 = Sse2.xor_si128(n3, v3);
|
||||
|
||||
Sse2.storeu_si128(output + 0x40, n0); //Store128_Byte(n0, output[0x40..]);
|
||||
Sse2.storeu_si128(output + 0x50, n1); //Store128_Byte(n1, output[0x50..]);
|
||||
Sse2.storeu_si128(output + 0x60, n2); //Store128_Byte(n2, output[0x60..]);
|
||||
Sse2.storeu_si128(output + 0x70, n3); //Store128_Byte(n3, output[0x70..]);
|
||||
}
|
||||
else if (Neon.IsNeonSupported)
|
||||
{
|
||||
var x0 = Neon.vld1q_u32(state); //new v128(state[0], state[1], state[2], state[3]); //Load128_UInt32(state.AsSpan());
|
||||
var x1 = Neon.vld1q_u32(state + 4); //new v128(state[4], state[5], state[6], state[7]); //Load128_UInt32(state.AsSpan(4));
|
||||
var x2 = Neon.vld1q_u32(state + 8); //new v128(state[8], state[9], state[10], state[11]); //Load128_UInt32(state.AsSpan(8));
|
||||
var x3 = Neon.vld1q_u32(state + 12);
|
||||
++state[12];
|
||||
|
||||
var v0 = x0;
|
||||
var v1 = x1;
|
||||
var v2 = x2;
|
||||
var v3 = x3;
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
v0 = Neon.vaddq_u32(v0, v1);
|
||||
v3 = Neon.veorq_u32(v3, v0);
|
||||
v3 = Neon.veorq_u32(Neon.vshlq_n_u32(v3, 16), Neon.vshrq_n_u32(v3, 16));
|
||||
v2 = Neon.vaddq_u32(v2, v3);
|
||||
v1 = Neon.veorq_u32(v1, v2);
|
||||
v1 = Neon.veorq_u32(Neon.vshlq_n_u32(v1, 12), Neon.vshrq_n_u32(v1, 20));
|
||||
v0 = Neon.vaddq_u32(v0, v1);
|
||||
v3 = Neon.veorq_u32(v3, v0);
|
||||
v3 = Neon.veorq_u32(Neon.vshlq_n_u32(v3, 8), Neon.vshrq_n_u32(v3, 24));
|
||||
v2 = Neon.vaddq_u32(v2, v3);
|
||||
v1 = Neon.veorq_u32(v1, v2);
|
||||
v1 = Neon.veorq_u32(Neon.vshlq_n_u32(v1, 7), Neon.vshrq_n_u32(v1, 25));
|
||||
|
||||
///*v1 = */Neon_shuffle_epi32(v1, 0x39, out v1);
|
||||
v128 ret;
|
||||
ret = Neon.vmovq_n_u32(Neon.vgetq_lane_u32(v1, (0x39) & 0x3));
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v1, ((0x39) >> 2) & 0x3), ret, 1);
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v1, ((0x39) >> 4) & 0x3), ret, 2);
|
||||
v1 = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v1, ((0x39) >> 6) & 0x3), ret, 3);
|
||||
|
||||
///*v2 = */Neon_shuffle_epi32(v2, 0x4E, out v2);
|
||||
ret = Neon.vmovq_n_u32(Neon.vgetq_lane_u32(v2, (0x4E) & 0x3));
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v2, ((0x4E) >> 2) & 0x3), ret, 1);
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v2, ((0x4E) >> 4) & 0x3), ret, 2);
|
||||
v2 = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v2, ((0x4E) >> 6) & 0x3), ret, 3);
|
||||
|
||||
///*v3 = */Neon_shuffle_epi32(v3, 0x93, out v3);
|
||||
ret = Neon.vmovq_n_u32(Neon.vgetq_lane_u32(v3, (0x93) & 0x3));
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v3, ((0x93) >> 2) & 0x3), ret, 1);
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v3, ((0x93) >> 4) & 0x3), ret, 2);
|
||||
v3 = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v3, ((0x93) >> 6) & 0x3), ret, 3);
|
||||
|
||||
v0 = Neon.vaddq_u32(v0, v1);
|
||||
v3 = Neon.veorq_u32(v3, v0);
|
||||
v3 = Neon.veorq_u32(Neon.vshlq_n_u32(v3, 16), Neon.vshrq_n_u32(v3, 16));
|
||||
v2 = Neon.vaddq_u32(v2, v3);
|
||||
v1 = Neon.veorq_u32(v1, v2);
|
||||
v1 = Neon.veorq_u32(Neon.vshlq_n_u32(v1, 12), Neon.vshrq_n_u32(v1, 20));
|
||||
v0 = Neon.vaddq_u32(v0, v1);
|
||||
v3 = Neon.veorq_u32(v3, v0);
|
||||
v3 = Neon.veorq_u32(Neon.vshlq_n_u32(v3, 8), Neon.vshrq_n_u32(v3, 24));
|
||||
v2 = Neon.vaddq_u32(v2, v3);
|
||||
v1 = Neon.veorq_u32(v1, v2);
|
||||
v1 = Neon.veorq_u32(Neon.vshlq_n_u32(v1, 7), Neon.vshrq_n_u32(v1, 25));
|
||||
|
||||
///*v1 = */Neon_shuffle_epi32(v1, 0x93, out v1);
|
||||
ret = Neon.vmovq_n_u32(Neon.vgetq_lane_u32(v1, (0x93) & 0x3));
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v1, ((0x93) >> 2) & 0x3), ret, 1);
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v1, ((0x93) >> 4) & 0x3), ret, 2);
|
||||
v1 = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v1, ((0x93) >> 6) & 0x3), ret, 3);
|
||||
|
||||
///*v2 = */Neon_shuffle_epi32(v2, 0x4E, out v2);
|
||||
ret = Neon.vmovq_n_u32(Neon.vgetq_lane_u32(v2, (0x4E) & 0x3));
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v2, ((0x4E) >> 2) & 0x3), ret, 1);
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v2, ((0x4E) >> 4) & 0x3), ret, 2);
|
||||
v2 = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v2, ((0x4E) >> 6) & 0x3), ret, 3);
|
||||
|
||||
///*v3 = */Neon_shuffle_epi32(v3, 0x39, out v3);
|
||||
ret = Neon.vmovq_n_u32(Neon.vgetq_lane_u32(v3, (0x39) & 0x3));
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v3, ((0x39) >> 2) & 0x3), ret, 1);
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v3, ((0x39) >> 4) & 0x3), ret, 2);
|
||||
v3 = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v3, ((0x39) >> 6) & 0x3), ret, 3);
|
||||
}
|
||||
|
||||
v0 = Neon.vaddq_u32(v0, x0);
|
||||
v1 = Neon.vaddq_u32(v1, x1);
|
||||
v2 = Neon.vaddq_u32(v2, x2);
|
||||
v3 = Neon.vaddq_u32(v3, x3);
|
||||
|
||||
var n0 = Neon.vld1q_u32((uint*)(input + 0x00)); //Load128_Byte(input);
|
||||
var n1 = Neon.vld1q_u32((uint*)(input + 0x10)); //Load128_Byte(input[0x10..]);
|
||||
var n2 = Neon.vld1q_u32((uint*)(input + 0x20)); //Load128_Byte(input[0x20..]);
|
||||
var n3 = Neon.vld1q_u32((uint*)(input + 0x30)); //Load128_Byte(input[0x30..]);
|
||||
|
||||
n0 = Neon.veorq_u32(n0, v0);
|
||||
n1 = Neon.veorq_u32(n1, v1);
|
||||
n2 = Neon.veorq_u32(n2, v2);
|
||||
n3 = Neon.veorq_u32(n3, v3);
|
||||
|
||||
Neon.vst1q_u32((uint*)(output + 0x00), n0); //Store128_Byte(n0, output);
|
||||
Neon.vst1q_u32((uint*)(output + 0x10), n1); //Store128_Byte(n1, output[0x10..]);
|
||||
Neon.vst1q_u32((uint*)(output + 0x20), n2); //Store128_Byte(n2, output[0x20..]);
|
||||
Neon.vst1q_u32((uint*)(output + 0x30), n3); //Store128_Byte(n3, output[0x30..]);
|
||||
|
||||
|
||||
x3 = Neon.vld1q_u32(state + 12); // Load128_UInt32(state.AsSpan(12));
|
||||
++state[12];
|
||||
|
||||
v0 = x0;
|
||||
v1 = x1;
|
||||
v2 = x2;
|
||||
v3 = x3;
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
v0 = Neon.vaddq_u32(v0, v1);
|
||||
v3 = Neon.veorq_u32(v3, v0);
|
||||
v3 = Neon.veorq_u32(Neon.vshlq_n_u32(v3, 16), Neon.vshrq_n_u32(v3, 16));
|
||||
v2 = Neon.vaddq_u32(v2, v3);
|
||||
v1 = Neon.veorq_u32(v1, v2);
|
||||
v1 = Neon.veorq_u32(Neon.vshlq_n_u32(v1, 12), Neon.vshrq_n_u32(v1, 20));
|
||||
v0 = Neon.vaddq_u32(v0, v1);
|
||||
v3 = Neon.veorq_u32(v3, v0);
|
||||
v3 = Neon.veorq_u32(Neon.vshlq_n_u32(v3, 8), Neon.vshrq_n_u32(v3, 24));
|
||||
v2 = Neon.vaddq_u32(v2, v3);
|
||||
v1 = Neon.veorq_u32(v1, v2);
|
||||
v1 = Neon.veorq_u32(Neon.vshlq_n_u32(v1, 7), Neon.vshrq_n_u32(v1, 25));
|
||||
|
||||
///*v1 = */Neon_shuffle_epi32(v1, 0x39, out v1);
|
||||
v128 ret;
|
||||
ret = Neon.vmovq_n_u32(Neon.vgetq_lane_u32(v1, (0x39) & 0x3));
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v1, ((0x39) >> 2) & 0x3), ret, 1);
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v1, ((0x39) >> 4) & 0x3), ret, 2);
|
||||
v1 = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v1, ((0x39) >> 6) & 0x3), ret, 3);
|
||||
|
||||
///*v2 = */Neon_shuffle_epi32(v2, 0x4E, out v2);
|
||||
ret = Neon.vmovq_n_u32(Neon.vgetq_lane_u32(v2, (0x4E) & 0x3));
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v2, ((0x4E) >> 2) & 0x3), ret, 1);
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v2, ((0x4E) >> 4) & 0x3), ret, 2);
|
||||
v2 = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v2, ((0x4E) >> 6) & 0x3), ret, 3);
|
||||
|
||||
///*v3 = */Neon_shuffle_epi32(v3, 0x93, out v3);
|
||||
ret = Neon.vmovq_n_u32(Neon.vgetq_lane_u32(v3, (0x93) & 0x3));
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v3, ((0x93) >> 2) & 0x3), ret, 1);
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v3, ((0x93) >> 4) & 0x3), ret, 2);
|
||||
v3 = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v3, ((0x93) >> 6) & 0x3), ret, 3);
|
||||
|
||||
v0 = Neon.vaddq_u32(v0, v1);
|
||||
v3 = Neon.veorq_u32(v3, v0);
|
||||
v3 = Neon.veorq_u32(Neon.vshlq_n_u32(v3, 16), Neon.vshrq_n_u32(v3, 16));
|
||||
v2 = Neon.vaddq_u32(v2, v3);
|
||||
v1 = Neon.veorq_u32(v1, v2);
|
||||
v1 = Neon.veorq_u32(Neon.vshlq_n_u32(v1, 12), Neon.vshrq_n_u32(v1, 20));
|
||||
v0 = Neon.vaddq_u32(v0, v1);
|
||||
v3 = Neon.veorq_u32(v3, v0);
|
||||
v3 = Neon.veorq_u32(Neon.vshlq_n_u32(v3, 8), Neon.vshrq_n_u32(v3, 24));
|
||||
v2 = Neon.vaddq_u32(v2, v3);
|
||||
v1 = Neon.veorq_u32(v1, v2);
|
||||
v1 = Neon.veorq_u32(Neon.vshlq_n_u32(v1, 7), Neon.vshrq_n_u32(v1, 25));
|
||||
|
||||
///*v1 = */Neon_shuffle_epi32(v1, 0x93, out v1);
|
||||
ret = Neon.vmovq_n_u32(Neon.vgetq_lane_u32(v1, (0x93) & 0x3));
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v1, ((0x93) >> 2) & 0x3), ret, 1);
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v1, ((0x93) >> 4) & 0x3), ret, 2);
|
||||
v1 = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v1, ((0x93) >> 6) & 0x3), ret, 3);
|
||||
|
||||
///*v2 = */Neon_shuffle_epi32(v2, 0x4E, out v2);
|
||||
ret = Neon.vmovq_n_u32(Neon.vgetq_lane_u32(v2, (0x4E) & 0x3));
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v2, ((0x4E) >> 2) & 0x3), ret, 1);
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v2, ((0x4E) >> 4) & 0x3), ret, 2);
|
||||
v2 = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v2, ((0x4E) >> 6) & 0x3), ret, 3);
|
||||
|
||||
///*v3 = */Neon_shuffle_epi32(v3, 0x39, out v3);
|
||||
ret = Neon.vmovq_n_u32(Neon.vgetq_lane_u32(v3, (0x39) & 0x3));
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v3, ((0x39) >> 2) & 0x3), ret, 1);
|
||||
ret = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v3, ((0x39) >> 4) & 0x3), ret, 2);
|
||||
v3 = Neon.vsetq_lane_u32(Neon.vgetq_lane_u32(v3, ((0x39) >> 6) & 0x3), ret, 3);
|
||||
}
|
||||
|
||||
v0 = Neon.vaddq_u32(v0, x0);
|
||||
v1 = Neon.vaddq_u32(v1, x1);
|
||||
v2 = Neon.vaddq_u32(v2, x2);
|
||||
v3 = Neon.vaddq_u32(v3, x3);
|
||||
|
||||
n0 = Neon.vld1q_u32((uint*)(input + 0x40)); //Load128_Byte(input[0x40..]);
|
||||
n1 = Neon.vld1q_u32((uint*)(input + 0x50)); //Load128_Byte(input[0x50..]);
|
||||
n2 = Neon.vld1q_u32((uint*)(input + 0x60)); //Load128_Byte(input[0x60..]);
|
||||
n3 = Neon.vld1q_u32((uint*)(input + 0x70)); //Load128_Byte(input[0x70..]);
|
||||
|
||||
n0 = Neon.veorq_u32(n0, v0);
|
||||
n1 = Neon.veorq_u32(n1, v1);
|
||||
n2 = Neon.veorq_u32(n2, v2);
|
||||
n3 = Neon.veorq_u32(n3, v3);
|
||||
|
||||
Neon.vst1q_u32((uint*)(output + 0x40), n0); //Store128_Byte(n0, output[0x40..]);
|
||||
Neon.vst1q_u32((uint*)(output + 0x50), n1); //Store128_Byte(n1, output[0x50..]);
|
||||
Neon.vst1q_u32((uint*)(output + 0x60), n2); //Store128_Byte(n2, output[0x60..]);
|
||||
Neon.vst1q_u32((uint*)(output + 0x70), n3); //Store128_Byte(n3, output[0x70..]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inlined to two ImplProcessBlock calls:
|
||||
//ImplProcessBlock(input, output);
|
||||
//ImplProcessBlock(input[64..], output[64..]);
|
||||
|
||||
FastChaChaEngineHelper.ChachaCoreImpl(rounds, state, keyStream);
|
||||
++state[12];
|
||||
ulong* pulinput = (ulong*)input;
|
||||
ulong* puloutput = (ulong*)output;
|
||||
ulong* pulkeyStream = (ulong*)keyStream;
|
||||
|
||||
puloutput[0] = pulkeyStream[0] ^ pulinput[0];
|
||||
puloutput[1] = pulkeyStream[1] ^ pulinput[1];
|
||||
puloutput[2] = pulkeyStream[2] ^ pulinput[2];
|
||||
puloutput[3] = pulkeyStream[3] ^ pulinput[3];
|
||||
|
||||
puloutput[4] = pulkeyStream[4] ^ pulinput[4];
|
||||
puloutput[5] = pulkeyStream[5] ^ pulinput[5];
|
||||
puloutput[6] = pulkeyStream[6] ^ pulinput[6];
|
||||
puloutput[7] = pulkeyStream[7] ^ pulinput[7];
|
||||
|
||||
FastChaChaEngineHelper.ChachaCoreImpl(rounds, state, keyStream);
|
||||
++state[12];
|
||||
|
||||
pulinput = (ulong*)&input[64];
|
||||
puloutput = (ulong*)&output[64];
|
||||
|
||||
puloutput[0] = pulkeyStream[0] ^ pulinput[0];
|
||||
puloutput[1] = pulkeyStream[1] ^ pulinput[1];
|
||||
puloutput[2] = pulkeyStream[2] ^ pulinput[2];
|
||||
puloutput[3] = pulkeyStream[3] ^ pulinput[3];
|
||||
|
||||
puloutput[4] = pulkeyStream[4] ^ pulinput[4];
|
||||
puloutput[5] = pulkeyStream[5] ^ pulinput[5];
|
||||
puloutput[6] = pulkeyStream[6] ^ pulinput[6];
|
||||
puloutput[7] = pulkeyStream[7] ^ pulinput[7];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
using System.Buffers.Binary;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of Daniel J. Bernstein's ChaCha stream cipher.
|
||||
/// </summary>
|
||||
|
||||
|
||||
|
||||
[BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
|
||||
public sealed class FastChaChaEngine
|
||||
: FastSalsa20Engine
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a 20 rounds ChaCha engine.
|
||||
/// </summary>
|
||||
public FastChaChaEngine()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ChaCha engine with a specific number of rounds.
|
||||
/// </summary>
|
||||
/// <param name="rounds">the number of rounds (must be an even number).</param>
|
||||
public FastChaChaEngine(int rounds)
|
||||
: base(rounds)
|
||||
{
|
||||
}
|
||||
|
||||
public override string AlgorithmName
|
||||
{
|
||||
get { return "ChaCha" + rounds; }
|
||||
}
|
||||
|
||||
protected override void AdvanceCounter()
|
||||
{
|
||||
if (++engineState[12] == 0)
|
||||
{
|
||||
++engineState[13];
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ResetCounter()
|
||||
{
|
||||
engineState[12] = engineState[13] = 0;
|
||||
}
|
||||
|
||||
protected override void SetKey(byte[] keyBytes, byte[] ivBytes)
|
||||
{
|
||||
if (keyBytes != null)
|
||||
{
|
||||
if ((keyBytes.Length != 16) && (keyBytes.Length != 32))
|
||||
throw new ArgumentException(AlgorithmName + " requires 128 bit or 256 bit key");
|
||||
|
||||
PackTauOrSigma(keyBytes.Length, engineState, 0);
|
||||
|
||||
// Key
|
||||
Pack.LE_To_UInt32(keyBytes, 0, engineState, 4, 4);
|
||||
Pack.LE_To_UInt32(keyBytes, keyBytes.Length - 16, engineState, 8, 4);
|
||||
}
|
||||
|
||||
// IV
|
||||
Pack.LE_To_UInt32(ivBytes, 0, engineState, 14, 2);
|
||||
}
|
||||
|
||||
protected override void GenerateKeyStream(byte[] output)
|
||||
{
|
||||
//ChachaCore(rounds, engineState, output);
|
||||
FastChaChaEngineHelper.ChachaCore(rounds, engineState, output);
|
||||
}
|
||||
|
||||
internal static void ChachaCore(int rounds, uint[] input, byte[] output)
|
||||
{
|
||||
Debug.Assert(rounds % 2 == 0);
|
||||
Debug.Assert(input.Length >= 16);
|
||||
Debug.Assert(output.Length >= 64);
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
if (Sse2.IsSupported)
|
||||
{
|
||||
var x0 = Load128_UInt32(input.AsSpan());
|
||||
var x1 = Load128_UInt32(input.AsSpan(4));
|
||||
var x2 = Load128_UInt32(input.AsSpan(8));
|
||||
var x3 = Load128_UInt32(input.AsSpan(12));
|
||||
|
||||
var v0 = x0;
|
||||
var v1 = x1;
|
||||
var v2 = x2;
|
||||
var v3 = x3;
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
v0 = Sse2.Add(v0, v1);
|
||||
v3 = Sse2.Xor(v3, v0);
|
||||
v3 = Sse2.Xor(Sse2.ShiftLeftLogical(v3, 16), Sse2.ShiftRightLogical(v3, 16));
|
||||
v2 = Sse2.Add(v2, v3);
|
||||
v1 = Sse2.Xor(v1, v2);
|
||||
v1 = Sse2.Xor(Sse2.ShiftLeftLogical(v1, 12), Sse2.ShiftRightLogical(v1, 20));
|
||||
v0 = Sse2.Add(v0, v1);
|
||||
v3 = Sse2.Xor(v3, v0);
|
||||
v3 = Sse2.Xor(Sse2.ShiftLeftLogical(v3, 8), Sse2.ShiftRightLogical(v3, 24));
|
||||
v2 = Sse2.Add(v2, v3);
|
||||
v1 = Sse2.Xor(v1, v2);
|
||||
v1 = Sse2.Xor(Sse2.ShiftLeftLogical(v1, 7), Sse2.ShiftRightLogical(v1, 25));
|
||||
|
||||
v1 = Sse2.Shuffle(v1, 0x39);
|
||||
v2 = Sse2.Shuffle(v2, 0x4E);
|
||||
v3 = Sse2.Shuffle(v3, 0x93);
|
||||
|
||||
v0 = Sse2.Add(v0, v1);
|
||||
v3 = Sse2.Xor(v3, v0);
|
||||
v3 = Sse2.Xor(Sse2.ShiftLeftLogical(v3, 16), Sse2.ShiftRightLogical(v3, 16));
|
||||
v2 = Sse2.Add(v2, v3);
|
||||
v1 = Sse2.Xor(v1, v2);
|
||||
v1 = Sse2.Xor(Sse2.ShiftLeftLogical(v1, 12), Sse2.ShiftRightLogical(v1, 20));
|
||||
v0 = Sse2.Add(v0, v1);
|
||||
v3 = Sse2.Xor(v3, v0);
|
||||
v3 = Sse2.Xor(Sse2.ShiftLeftLogical(v3, 8), Sse2.ShiftRightLogical(v3, 24));
|
||||
v2 = Sse2.Add(v2, v3);
|
||||
v1 = Sse2.Xor(v1, v2);
|
||||
v1 = Sse2.Xor(Sse2.ShiftLeftLogical(v1, 7), Sse2.ShiftRightLogical(v1, 25));
|
||||
|
||||
v1 = Sse2.Shuffle(v1, 0x93);
|
||||
v2 = Sse2.Shuffle(v2, 0x4E);
|
||||
v3 = Sse2.Shuffle(v3, 0x39);
|
||||
}
|
||||
|
||||
v0 = Sse2.Add(v0, x0);
|
||||
v1 = Sse2.Add(v1, x1);
|
||||
v2 = Sse2.Add(v2, x2);
|
||||
v3 = Sse2.Add(v3, x3);
|
||||
|
||||
Store128_UInt32(v0, output.AsSpan());
|
||||
Store128_UInt32(v1, output.AsSpan(0x10));
|
||||
Store128_UInt32(v2, output.AsSpan(0x20));
|
||||
Store128_UInt32(v3, output.AsSpan(0x30));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
uint x00 = input[0], x01 = input[1], x02 = input[2], x03 = input[3];
|
||||
uint x04 = input[4], x05 = input[5], x06 = input[6], x07 = input[7];
|
||||
uint x08 = input[8], x09 = input[9], x10 = input[10], x11 = input[11];
|
||||
uint x12 = input[12], x13 = input[13], x14 = input[14], x15 = input[15];
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
x00 += x04; x12 = Integers.RotateLeft(x12 ^ x00, 16);
|
||||
x01 += x05; x13 = Integers.RotateLeft(x13 ^ x01, 16);
|
||||
x02 += x06; x14 = Integers.RotateLeft(x14 ^ x02, 16);
|
||||
x03 += x07; x15 = Integers.RotateLeft(x15 ^ x03, 16);
|
||||
|
||||
x08 += x12; x04 = Integers.RotateLeft(x04 ^ x08, 12);
|
||||
x09 += x13; x05 = Integers.RotateLeft(x05 ^ x09, 12);
|
||||
x10 += x14; x06 = Integers.RotateLeft(x06 ^ x10, 12);
|
||||
x11 += x15; x07 = Integers.RotateLeft(x07 ^ x11, 12);
|
||||
|
||||
x00 += x04; x12 = Integers.RotateLeft(x12 ^ x00, 8);
|
||||
x01 += x05; x13 = Integers.RotateLeft(x13 ^ x01, 8);
|
||||
x02 += x06; x14 = Integers.RotateLeft(x14 ^ x02, 8);
|
||||
x03 += x07; x15 = Integers.RotateLeft(x15 ^ x03, 8);
|
||||
|
||||
x08 += x12; x04 = Integers.RotateLeft(x04 ^ x08, 7);
|
||||
x09 += x13; x05 = Integers.RotateLeft(x05 ^ x09, 7);
|
||||
x10 += x14; x06 = Integers.RotateLeft(x06 ^ x10, 7);
|
||||
x11 += x15; x07 = Integers.RotateLeft(x07 ^ x11, 7);
|
||||
x00 += x05; x15 = Integers.RotateLeft(x15 ^ x00, 16);
|
||||
x01 += x06; x12 = Integers.RotateLeft(x12 ^ x01, 16);
|
||||
x02 += x07; x13 = Integers.RotateLeft(x13 ^ x02, 16);
|
||||
x03 += x04; x14 = Integers.RotateLeft(x14 ^ x03, 16);
|
||||
|
||||
x10 += x15; x05 = Integers.RotateLeft(x05 ^ x10, 12);
|
||||
x11 += x12; x06 = Integers.RotateLeft(x06 ^ x11, 12);
|
||||
x08 += x13; x07 = Integers.RotateLeft(x07 ^ x08, 12);
|
||||
x09 += x14; x04 = Integers.RotateLeft(x04 ^ x09, 12);
|
||||
|
||||
x00 += x05; x15 = Integers.RotateLeft(x15 ^ x00, 8);
|
||||
x01 += x06; x12 = Integers.RotateLeft(x12 ^ x01, 8);
|
||||
x02 += x07; x13 = Integers.RotateLeft(x13 ^ x02, 8);
|
||||
x03 += x04; x14 = Integers.RotateLeft(x14 ^ x03, 8);
|
||||
|
||||
x10 += x15; x05 = Integers.RotateLeft(x05 ^ x10, 7);
|
||||
x11 += x12; x06 = Integers.RotateLeft(x06 ^ x11, 7);
|
||||
x08 += x13; x07 = Integers.RotateLeft(x07 ^ x08, 7);
|
||||
x09 += x14; x04 = Integers.RotateLeft(x04 ^ x09, 7);
|
||||
}
|
||||
|
||||
Pack.UInt32_To_LE(x00 + input[0], output, 0);
|
||||
Pack.UInt32_To_LE(x01 + input[1], output, 4);
|
||||
Pack.UInt32_To_LE(x02 + input[2], output, 8);
|
||||
Pack.UInt32_To_LE(x03 + input[3], output, 12);
|
||||
Pack.UInt32_To_LE(x04 + input[4], output, 16);
|
||||
Pack.UInt32_To_LE(x05 + input[5], output, 20);
|
||||
Pack.UInt32_To_LE(x06 + input[6], output, 24);
|
||||
Pack.UInt32_To_LE(x07 + input[7], output, 28);
|
||||
Pack.UInt32_To_LE(x08 + input[8], output, 32);
|
||||
Pack.UInt32_To_LE(x09 + input[9], output, 36);
|
||||
Pack.UInt32_To_LE(x10 + input[10], output, 40);
|
||||
Pack.UInt32_To_LE(x11 + input[11], output, 44);
|
||||
Pack.UInt32_To_LE(x12 + input[12], output, 48);
|
||||
Pack.UInt32_To_LE(x13 + input[13], output, 52);
|
||||
Pack.UInt32_To_LE(x14 + input[14], output, 56);
|
||||
Pack.UInt32_To_LE(x15 + input[15], output, 60);
|
||||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector128<uint> Load128_UInt32(ReadOnlySpan<uint> t)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<uint>>() == 16)
|
||||
return MemoryMarshal.Read<Vector128<uint>>(MemoryMarshal.AsBytes(t));
|
||||
|
||||
return Vector128.Create(t[0], t[1], t[2], t[3]);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Store128_UInt32(Vector128<uint> s, Span<byte> t)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<uint>>() == 16)
|
||||
{
|
||||
MemoryMarshal.Write(t, ref s);
|
||||
return;
|
||||
}
|
||||
|
||||
var u = s.AsUInt64();
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(t[..8], u.GetElement(0));
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(t[8..], u.GetElement(1));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
|
||||
|
||||
#if BESTHTTP_WITH_BURST
|
||||
using Unity.Burst;
|
||||
#endif
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
#if BESTHTTP_WITH_BURST
|
||||
[Unity.Burst.BurstCompile]
|
||||
#endif
|
||||
internal static class FastChaChaEngineHelper
|
||||
{
|
||||
internal unsafe static void ChachaCore(int rounds, uint[] input, byte[] output)
|
||||
{
|
||||
fixed (uint* pinput = input)
|
||||
fixed (byte* poutput = output)
|
||||
ChachaCoreImpl(rounds, pinput, poutput);
|
||||
}
|
||||
|
||||
#if BESTHTTP_WITH_BURST
|
||||
[Unity.Burst.BurstCompile]
|
||||
[Unity.Burst.CompilerServices.SkipLocalsInit]
|
||||
#endif
|
||||
internal unsafe static void ChachaCoreImpl(int rounds,
|
||||
#if BESTHTTP_WITH_BURST
|
||||
[NoAlias]
|
||||
#endif
|
||||
uint* input,
|
||||
#if BESTHTTP_WITH_BURST
|
||||
[NoAlias]
|
||||
#endif
|
||||
byte* output)
|
||||
{
|
||||
uint* x = stackalloc uint[16];
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
x[i] = input[i];
|
||||
|
||||
uint tmp = 0;
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
x[00] += x[04]; tmp = x[12] ^ x[00]; x[12] = (tmp << 16) | (tmp >> -16); // Integers.RotateLeft(x[12] ^ x[00], 16);
|
||||
x[01] += x[05]; tmp = x[13] ^ x[01]; x[13] = (tmp << 16) | (tmp >> -16); // Integers.RotateLeft(x[13] ^ x[01], 16);
|
||||
x[02] += x[06]; tmp = x[14] ^ x[02]; x[14] = (tmp << 16) | (tmp >> -16); // Integers.RotateLeft(x[14] ^ x[02], 16);
|
||||
x[03] += x[07]; tmp = x[15] ^ x[03]; x[15] = (tmp << 16) | (tmp >> -16); // Integers.RotateLeft(x[15] ^ x[03], 16);
|
||||
|
||||
x[08] += x[12]; tmp = x[04] ^ x[08]; x[04] = (tmp << 12) | (tmp >> -12); // Integers.RotateLeft(x[04] ^ x[08], 12);
|
||||
x[09] += x[13]; tmp = x[05] ^ x[09]; x[05] = (tmp << 12) | (tmp >> -12); // Integers.RotateLeft(x[05] ^ x[09], 12);
|
||||
x[10] += x[14]; tmp = x[06] ^ x[10]; x[06] = (tmp << 12) | (tmp >> -12); // Integers.RotateLeft(x[06] ^ x[10], 12);
|
||||
x[11] += x[15]; tmp = x[07] ^ x[11]; x[07] = (tmp << 12) | (tmp >> -12); // Integers.RotateLeft(x[07] ^ x[11], 12);
|
||||
|
||||
x[00] += x[04]; tmp = x[12] ^ x[00]; x[12] = (tmp << 8) | (tmp >> -8); // Integers.RotateLeft(x[12] ^ x[00], 8);
|
||||
x[01] += x[05]; tmp = x[13] ^ x[01]; x[13] = (tmp << 8) | (tmp >> -8); // Integers.RotateLeft(x[13] ^ x[01], 8);
|
||||
x[02] += x[06]; tmp = x[14] ^ x[02]; x[14] = (tmp << 8) | (tmp >> -8); // Integers.RotateLeft(x[14] ^ x[02], 8);
|
||||
x[03] += x[07]; tmp = x[15] ^ x[03]; x[15] = (tmp << 8) | (tmp >> -8); // Integers.RotateLeft(x[15] ^ x[03], 8);
|
||||
|
||||
x[08] += x[12]; tmp = x[04] ^ x[08]; x[04] = (tmp << 7) | (tmp >> -7); // Integers.RotateLeft(x[04] ^ x[08], 7);
|
||||
x[09] += x[13]; tmp = x[05] ^ x[09]; x[05] = (tmp << 7) | (tmp >> -7); // Integers.RotateLeft(x[05] ^ x[09], 7);
|
||||
x[10] += x[14]; tmp = x[06] ^ x[10]; x[06] = (tmp << 7) | (tmp >> -7); // Integers.RotateLeft(x[06] ^ x[10], 7);
|
||||
x[11] += x[15]; tmp = x[07] ^ x[11]; x[07] = (tmp << 7) | (tmp >> -7); // Integers.RotateLeft(x[07] ^ x[11], 7);
|
||||
x[00] += x[05]; tmp = x[15] ^ x[00]; x[15] = (tmp << 16) | (tmp >> -16); // Integers.RotateLeft(x[15] ^ x[00], 16);
|
||||
x[01] += x[06]; tmp = x[12] ^ x[01]; x[12] = (tmp << 16) | (tmp >> -16); // Integers.RotateLeft(x[12] ^ x[01], 16);
|
||||
x[02] += x[07]; tmp = x[13] ^ x[02]; x[13] = (tmp << 16) | (tmp >> -16); // Integers.RotateLeft(x[13] ^ x[02], 16);
|
||||
x[03] += x[04]; tmp = x[14] ^ x[03]; x[14] = (tmp << 16) | (tmp >> -16); // Integers.RotateLeft(x[14] ^ x[03], 16);
|
||||
|
||||
x[10] += x[15]; tmp = x[05] ^ x[10]; x[05] = (tmp << 12) | (tmp >> -12); // Integers.RotateLeft(x[05] ^ x[10], 12);
|
||||
x[11] += x[12]; tmp = x[06] ^ x[11]; x[06] = (tmp << 12) | (tmp >> -12); // Integers.RotateLeft(x[06] ^ x[11], 12);
|
||||
x[08] += x[13]; tmp = x[07] ^ x[08]; x[07] = (tmp << 12) | (tmp >> -12); // Integers.RotateLeft(x[07] ^ x[08], 12);
|
||||
x[09] += x[14]; tmp = x[04] ^ x[09]; x[04] = (tmp << 12) | (tmp >> -12); // Integers.RotateLeft(x[04] ^ x[09], 12);
|
||||
|
||||
x[00] += x[05]; tmp = x[15] ^ x[00]; x[15] = (tmp << 8) | (tmp >> -8); // Integers.RotateLeft(x[15] ^ x[00], 8);
|
||||
x[01] += x[06]; tmp = x[12] ^ x[01]; x[12] = (tmp << 8) | (tmp >> -8); // Integers.RotateLeft(x[12] ^ x[01], 8);
|
||||
x[02] += x[07]; tmp = x[13] ^ x[02]; x[13] = (tmp << 8) | (tmp >> -8); // Integers.RotateLeft(x[13] ^ x[02], 8);
|
||||
x[03] += x[04]; tmp = x[14] ^ x[03]; x[14] = (tmp << 8) | (tmp >> -8); // Integers.RotateLeft(x[14] ^ x[03], 8);
|
||||
|
||||
x[10] += x[15]; tmp = x[05] ^ x[10]; x[05] = (tmp << 7) | (tmp >> -7); // Integers.RotateLeft(x[05] ^ x[10], 7);
|
||||
x[11] += x[12]; tmp = x[06] ^ x[11]; x[06] = (tmp << 7) | (tmp >> -7); // Integers.RotateLeft(x[06] ^ x[11], 7);
|
||||
x[08] += x[13]; tmp = x[07] ^ x[08]; x[07] = (tmp << 7) | (tmp >> -7); // Integers.RotateLeft(x[07] ^ x[08], 7);
|
||||
x[09] += x[14]; tmp = x[04] ^ x[09]; x[04] = (tmp << 7) | (tmp >> -7); // Integers.RotateLeft(x[04] ^ x[09], 7);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
uint n = x[i] + input[i];
|
||||
|
||||
output[(i * 4)] = (byte)n;
|
||||
output[(i * 4) + 1] = (byte)(n >> 8);
|
||||
output[(i * 4) + 2] = (byte)(n >> 16);
|
||||
output[(i * 4) + 3] = (byte)(n >> 24);
|
||||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static unsafe void ImplProcessBlock(ReadOnlySpan<byte> input, Span<byte> output, byte[] keyStream)
|
||||
{
|
||||
fixed (byte* pinput = input)
|
||||
fixed (byte* poutput = output)
|
||||
fixed (byte* pkeyStream = keyStream)
|
||||
{
|
||||
ulong* pulinput = (ulong*)pinput;
|
||||
ulong* puloutput = (ulong*)poutput;
|
||||
ulong* pulkeyStream = (ulong*)pkeyStream;
|
||||
|
||||
puloutput[0] = pulkeyStream[0] ^ pulinput[0];
|
||||
puloutput[1] = pulkeyStream[1] ^ pulinput[1];
|
||||
puloutput[2] = pulkeyStream[2] ^ pulinput[2];
|
||||
puloutput[3] = pulkeyStream[3] ^ pulinput[3];
|
||||
|
||||
puloutput[4] = pulkeyStream[4] ^ pulinput[4];
|
||||
puloutput[5] = pulkeyStream[5] ^ pulinput[5];
|
||||
puloutput[6] = pulkeyStream[6] ^ pulinput[6];
|
||||
puloutput[7] = pulkeyStream[7] ^ pulinput[7];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,78 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) && BESTHTTP_WITH_BURST
|
||||
using System;
|
||||
using static Unity.Burst.Intrinsics.X86.Sse2;
|
||||
using static Unity.Burst.Intrinsics.Arm.Neon;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Burst;
|
||||
|
||||
// https://github.com/sschoener/burst-simd-exercises/blob/main/Assets/Examples/2-sum-small-numbers-sse3/SumSmallNumbers_SSE3.cs
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
[BurstCompile]
|
||||
internal sealed unsafe class FastGcmBlockCipherHelper
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe void DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output, Span<byte> ctrBlock, Span<byte> S, int BlockSize)
|
||||
{
|
||||
fixed (byte* pInput = input)
|
||||
fixed (byte* pOutput = output)
|
||||
fixed (byte* pctrBlock = ctrBlock)
|
||||
fixed (byte* pS = S) {
|
||||
DecryptBlock_Impl(pInput, input.Length, pOutput, output.Length, pctrBlock, pS, BlockSize);
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile(CompileSynchronously = true)]
|
||||
private unsafe static void DecryptBlock_Impl([NoAlias] byte* pinput, int inLen, [NoAlias] byte* poutput, int outLen, [NoAlias] byte* pctrBlock, [NoAlias] byte* pS, int BlockSize)
|
||||
{
|
||||
if (IsSse2Supported)
|
||||
{
|
||||
var vInput = loadu_si128(pinput);
|
||||
var vCtrBlock = loadu_si128(pctrBlock);
|
||||
var vS = loadu_si128(pS);
|
||||
|
||||
vS = xor_si128(vS, vInput);
|
||||
vCtrBlock = xor_si128(vInput, vCtrBlock);
|
||||
|
||||
storeu_si128(pS, vS);
|
||||
storeu_si128(poutput, vCtrBlock);
|
||||
}
|
||||
else if (IsNeonSupported)
|
||||
{
|
||||
var vInput = vld1q_u8(pinput);
|
||||
var vCtrBlock = vld1q_u8(pctrBlock);
|
||||
var vS = vld1q_u8(pS);
|
||||
|
||||
vS = veorq_u8(vS, vInput);
|
||||
vCtrBlock = veorq_u8(vInput, vCtrBlock);
|
||||
|
||||
vst1q_u8(pS, vS);
|
||||
vst1q_u8(poutput, vCtrBlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
Unity.Burst.CompilerServices.Hint.Assume(BlockSize == 16);
|
||||
|
||||
for (int i = 0; i < BlockSize; i += 4)
|
||||
{
|
||||
byte c0 = pinput[i + 0];
|
||||
byte c1 = pinput[i + 1];
|
||||
byte c2 = pinput[i + 2];
|
||||
byte c3 = pinput[i + 3];
|
||||
|
||||
pS[i + 0] ^= c0;
|
||||
pS[i + 1] ^= c1;
|
||||
pS[i + 2] ^= c2;
|
||||
pS[i + 3] ^= c3;
|
||||
|
||||
poutput[i + 0] = (byte)(c0 ^ pctrBlock[i + 0]);
|
||||
poutput[i + 1] = (byte)(c1 ^ pctrBlock[i + 1]);
|
||||
poutput[i + 2] = (byte)(c2 ^ pctrBlock[i + 2]);
|
||||
poutput[i + 3] = (byte)(c3 ^ pctrBlock[i + 3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,438 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Generators;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Poly1305 message authentication code, designed by D. J. Bernstein.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Poly1305 computes a 128-bit (16 bytes) authenticator, using a 128 bit nonce and a 256 bit key
|
||||
/// consisting of a 128 bit key applied to an underlying cipher, and a 128 bit key (with 106
|
||||
/// effective key bits) used in the authenticator.
|
||||
///
|
||||
/// The polynomial calculation in this implementation is adapted from the public domain <a
|
||||
/// href="https://github.com/floodyberry/poly1305-donna">poly1305-donna-unrolled</a> C implementation
|
||||
/// by Andrew M (@floodyberry).
|
||||
/// </remarks>
|
||||
/// <seealso cref="BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Generators.Poly1305KeyGenerator"/>
|
||||
|
||||
|
||||
|
||||
[BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
|
||||
public sealed class FastPoly1305 : IMac
|
||||
{
|
||||
private const int BlockSize = 16;
|
||||
|
||||
private readonly IBlockCipher cipher;
|
||||
|
||||
// Initialised state
|
||||
|
||||
/** Polynomial key */
|
||||
private uint r0, r1, r2, r3, r4;
|
||||
|
||||
/** Precomputed 5 * r[1..4] */
|
||||
private uint s1, s2, s3, s4;
|
||||
|
||||
/** Encrypted nonce */
|
||||
private uint k0, k1, k2, k3;
|
||||
|
||||
// Accumulating state
|
||||
|
||||
/** Current block of buffered input */
|
||||
private byte[] currentBlock = new byte[BlockSize];
|
||||
|
||||
/** Current offset in input buffer */
|
||||
private int currentBlockOffset = 0;
|
||||
|
||||
/** Polynomial accumulator */
|
||||
private uint h0, h1, h2, h3, h4;
|
||||
|
||||
/**
|
||||
* Constructs a Poly1305 MAC, where the key passed to init() will be used directly.
|
||||
*/
|
||||
public FastPoly1305()
|
||||
{
|
||||
this.cipher = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Poly1305 MAC, using a 128 bit block cipher.
|
||||
*/
|
||||
public FastPoly1305(IBlockCipher cipher)
|
||||
{
|
||||
if (cipher.GetBlockSize() != BlockSize)
|
||||
{
|
||||
throw new ArgumentException("Poly1305 requires a 128 bit block cipher.");
|
||||
}
|
||||
this.cipher = cipher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialises the Poly1305 MAC.
|
||||
/// </summary>
|
||||
/// <param name="parameters">a {@link ParametersWithIV} containing a 128 bit nonce and a {@link KeyParameter} with
|
||||
/// a 256 bit key complying to the {@link Poly1305KeyGenerator Poly1305 key format}.</param>
|
||||
public void Init(ICipherParameters parameters)
|
||||
{
|
||||
byte[] nonce = null;
|
||||
|
||||
if (cipher != null)
|
||||
{
|
||||
if (!(parameters is ParametersWithIV))
|
||||
throw new ArgumentException("Poly1305 requires an IV when used with a block cipher.", "parameters");
|
||||
|
||||
ParametersWithIV ivParams = (ParametersWithIV)parameters;
|
||||
nonce = ivParams.GetIV();
|
||||
parameters = ivParams.Parameters;
|
||||
}
|
||||
|
||||
if (!(parameters is KeyParameter))
|
||||
throw new ArgumentException("Poly1305 requires a key.");
|
||||
|
||||
KeyParameter keyParams = (KeyParameter)parameters;
|
||||
|
||||
SetKey(keyParams.GetKey(), nonce);
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void SetKey(byte[] key, byte[] nonce)
|
||||
{
|
||||
if (key.Length != 32)
|
||||
throw new ArgumentException("Poly1305 key must be 256 bits.");
|
||||
|
||||
if (cipher != null && (nonce == null || nonce.Length != BlockSize))
|
||||
throw new ArgumentException("Poly1305 requires a 128 bit IV.");
|
||||
|
||||
// Extract r portion of key (and "clamp" the values)
|
||||
uint t0 = Pack.LE_To_UInt32(key, 0);
|
||||
uint t1 = Pack.LE_To_UInt32(key, 4);
|
||||
uint t2 = Pack.LE_To_UInt32(key, 8);
|
||||
uint t3 = Pack.LE_To_UInt32(key, 12);
|
||||
|
||||
// NOTE: The masks perform the key "clamping" implicitly
|
||||
r0 = t0 & 0x03FFFFFFU;
|
||||
r1 = ((t0 >> 26) | (t1 << 6)) & 0x03FFFF03U;
|
||||
r2 = ((t1 >> 20) | (t2 << 12)) & 0x03FFC0FFU;
|
||||
r3 = ((t2 >> 14) | (t3 << 18)) & 0x03F03FFFU;
|
||||
r4 = (t3 >> 8) & 0x000FFFFFU;
|
||||
|
||||
// Precompute multipliers
|
||||
s1 = r1 * 5;
|
||||
s2 = r2 * 5;
|
||||
s3 = r3 * 5;
|
||||
s4 = r4 * 5;
|
||||
|
||||
byte[] kBytes;
|
||||
int kOff;
|
||||
|
||||
if (cipher == null)
|
||||
{
|
||||
kBytes = key;
|
||||
kOff = BlockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compute encrypted nonce
|
||||
kBytes = new byte[BlockSize];
|
||||
kOff = 0;
|
||||
|
||||
cipher.Init(true, new KeyParameter(key, BlockSize, BlockSize));
|
||||
cipher.ProcessBlock(nonce, 0, kBytes, 0);
|
||||
}
|
||||
|
||||
k0 = Pack.LE_To_UInt32(kBytes, kOff + 0);
|
||||
k1 = Pack.LE_To_UInt32(kBytes, kOff + 4);
|
||||
k2 = Pack.LE_To_UInt32(kBytes, kOff + 8);
|
||||
k3 = Pack.LE_To_UInt32(kBytes, kOff + 12);
|
||||
}
|
||||
|
||||
public string AlgorithmName
|
||||
{
|
||||
get { return cipher == null ? "Poly1305" : "Poly1305-" + cipher.AlgorithmName; }
|
||||
}
|
||||
|
||||
public int GetMacSize()
|
||||
{
|
||||
return BlockSize;
|
||||
}
|
||||
|
||||
public void Update(byte input)
|
||||
{
|
||||
currentBlock[currentBlockOffset++] = input;
|
||||
if (currentBlockOffset == BlockSize)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
ProcessBlock(currentBlock);
|
||||
#else
|
||||
ProcessBlock(currentBlock, 0);
|
||||
#endif
|
||||
currentBlockOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
public void BlockUpdate(byte[] input, int inOff, int len)
|
||||
{
|
||||
Check.DataLength(input, inOff, len, "input buffer too short");
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
BlockUpdate(input.AsSpan(inOff, len));
|
||||
#else
|
||||
int available = BlockSize - currentBlockOffset;
|
||||
if (len < available)
|
||||
{
|
||||
Array.Copy(input, inOff, currentBlock, currentBlockOffset, len);
|
||||
currentBlockOffset += len;
|
||||
return;
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
if (currentBlockOffset > 0)
|
||||
{
|
||||
Array.Copy(input, inOff, currentBlock, currentBlockOffset, available);
|
||||
pos = available;
|
||||
ProcessBlock(currentBlock, 0);
|
||||
}
|
||||
|
||||
int remaining;
|
||||
while ((remaining = len - pos) >= BlockSize)
|
||||
{
|
||||
ProcessBlock(input, inOff + pos);
|
||||
pos += BlockSize;
|
||||
}
|
||||
|
||||
Array.Copy(input, inOff + pos, currentBlock, 0, remaining);
|
||||
currentBlockOffset = remaining;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#if !UNITY_ANDROID || UNITY_EDITOR
|
||||
unsafe
|
||||
#endif
|
||||
public void BlockUpdate(ReadOnlySpan<byte> input)
|
||||
{
|
||||
int available = BlockSize - currentBlockOffset;
|
||||
if (input.Length < available)
|
||||
{
|
||||
input.CopyTo(currentBlock.AsSpan(currentBlockOffset));
|
||||
currentBlockOffset += input.Length;
|
||||
return;
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
if (currentBlockOffset > 0)
|
||||
{
|
||||
input[..available].CopyTo(currentBlock.AsSpan(currentBlockOffset));
|
||||
pos = available;
|
||||
ProcessBlock(currentBlock);
|
||||
}
|
||||
|
||||
int remaining;
|
||||
while ((remaining = input.Length - pos) >= BlockSize)
|
||||
{
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
uint t0 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(input[pos..]);
|
||||
uint t1 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(input[(pos + 4)..]);
|
||||
uint t2 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(input[(pos + 8)..]);
|
||||
uint t3 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(input[(pos + 12)..]);
|
||||
#else
|
||||
uint t0 = 0;
|
||||
uint t1 = 0;
|
||||
uint t2 = 0;
|
||||
uint t3 = 0;
|
||||
|
||||
fixed (byte* pblock = &input[pos])
|
||||
{
|
||||
uint* publock = (uint*)pblock;
|
||||
t0 = publock[0];
|
||||
t1 = publock[1];
|
||||
t2 = publock[2];
|
||||
t3 = publock[3];
|
||||
}
|
||||
#endif
|
||||
|
||||
h0 += t0 & 0x3ffffffU;
|
||||
h1 += ((t1 << 6) | (t0 >> 26)) & 0x3ffffffU;
|
||||
h2 += ((t2 << 12) | (t1 >> 20)) & 0x3ffffffU;
|
||||
h3 += ((t3 << 18) | (t2 >> 14)) & 0x3ffffffU;
|
||||
h4 += (1 << 24) | (t3 >> 8);
|
||||
|
||||
ulong tp0 = (ulong)h0 * r0 + (ulong)h1 * s4 + (ulong)h2 * s3 + (ulong)h3 * s2 + (ulong)h4 * s1;
|
||||
ulong tp1 = (ulong)h0 * r1 + (ulong)h1 * r0 + (ulong)h2 * s4 + (ulong)h3 * s3 + (ulong)h4 * s2;
|
||||
ulong tp2 = (ulong)h0 * r2 + (ulong)h1 * r1 + (ulong)h2 * r0 + (ulong)h3 * s4 + (ulong)h4 * s3;
|
||||
ulong tp3 = (ulong)h0 * r3 + (ulong)h1 * r2 + (ulong)h2 * r1 + (ulong)h3 * r0 + (ulong)h4 * s4;
|
||||
ulong tp4 = (ulong)h0 * r4 + (ulong)h1 * r3 + (ulong)h2 * r2 + (ulong)h3 * r1 + (ulong)h4 * r0;
|
||||
|
||||
h0 = (uint)tp0 & 0x3ffffff; tp1 += (tp0 >> 26);
|
||||
h1 = (uint)tp1 & 0x3ffffff; tp2 += (tp1 >> 26);
|
||||
h2 = (uint)tp2 & 0x3ffffff; tp3 += (tp2 >> 26);
|
||||
h3 = (uint)tp3 & 0x3ffffff; tp4 += (tp3 >> 26);
|
||||
h4 = (uint)tp4 & 0x3ffffff;
|
||||
h0 += (uint)(tp4 >> 26) * 5;
|
||||
h1 += h0 >> 26; h0 &= 0x3ffffff;
|
||||
|
||||
pos += BlockSize;
|
||||
}
|
||||
|
||||
input[pos..].CopyTo(currentBlock);
|
||||
currentBlockOffset = remaining;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ProcessBlock(ReadOnlySpan<byte> block)
|
||||
{
|
||||
uint t0 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(block);
|
||||
uint t1 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(block[4..]);
|
||||
uint t2 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(block[8..]);
|
||||
uint t3 = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(block[12..]);
|
||||
#else
|
||||
private void ProcessBlock(byte[] buf, int off)
|
||||
{
|
||||
uint t0 = Pack.LE_To_UInt32(buf, off + 0);
|
||||
uint t1 = Pack.LE_To_UInt32(buf, off + 4);
|
||||
uint t2 = Pack.LE_To_UInt32(buf, off + 8);
|
||||
uint t3 = Pack.LE_To_UInt32(buf, off + 12);
|
||||
#endif
|
||||
|
||||
h0 += t0 & 0x3ffffffU;
|
||||
h1 += ((t1 << 6) | (t0 >> 26)) & 0x3ffffffU;
|
||||
h2 += ((t2 << 12) | (t1 >> 20)) & 0x3ffffffU;
|
||||
h3 += ((t3 << 18) | (t2 >> 14)) & 0x3ffffffU;
|
||||
h4 += (1 << 24) | (t3 >> 8);
|
||||
|
||||
ulong tp0 = (ulong)h0 * r0 + (ulong)h1 * s4 + (ulong)h2 * s3 + (ulong)h3 * s2 + (ulong)h4 * s1;
|
||||
ulong tp1 = (ulong)h0 * r1 + (ulong)h1 * r0 + (ulong)h2 * s4 + (ulong)h3 * s3 + (ulong)h4 * s2;
|
||||
ulong tp2 = (ulong)h0 * r2 + (ulong)h1 * r1 + (ulong)h2 * r0 + (ulong)h3 * s4 + (ulong)h4 * s3;
|
||||
ulong tp3 = (ulong)h0 * r3 + (ulong)h1 * r2 + (ulong)h2 * r1 + (ulong)h3 * r0 + (ulong)h4 * s4;
|
||||
ulong tp4 = (ulong)h0 * r4 + (ulong)h1 * r3 + (ulong)h2 * r2 + (ulong)h3 * r1 + (ulong)h4 * r0;
|
||||
|
||||
h0 = (uint)tp0 & 0x3ffffff; tp1 += (tp0 >> 26);
|
||||
h1 = (uint)tp1 & 0x3ffffff; tp2 += (tp1 >> 26);
|
||||
h2 = (uint)tp2 & 0x3ffffff; tp3 += (tp2 >> 26);
|
||||
h3 = (uint)tp3 & 0x3ffffff; tp4 += (tp3 >> 26);
|
||||
h4 = (uint)tp4 & 0x3ffffff;
|
||||
h0 += (uint)(tp4 >> 26) * 5;
|
||||
h1 += h0 >> 26; h0 &= 0x3ffffff;
|
||||
}
|
||||
|
||||
public int DoFinal(byte[] output, int outOff)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
return DoFinal(output.AsSpan(outOff));
|
||||
#else
|
||||
Check.OutputLength(output, outOff, BlockSize, "output buffer is too short.");
|
||||
|
||||
if (currentBlockOffset > 0)
|
||||
{
|
||||
// Process padded block
|
||||
if (currentBlockOffset < BlockSize)
|
||||
{
|
||||
currentBlock[currentBlockOffset++] = 1;
|
||||
while (currentBlockOffset < BlockSize)
|
||||
{
|
||||
currentBlock[currentBlockOffset++] = 0;
|
||||
}
|
||||
|
||||
h4 -= (1 << 24);
|
||||
}
|
||||
|
||||
ProcessBlock(currentBlock, 0);
|
||||
}
|
||||
|
||||
UnityEngine.Debug.Assert(h4 >> 26 == 0);
|
||||
|
||||
//h0 += (h4 >> 26) * 5U + 5U; h4 &= 0x3ffffff;
|
||||
h0 += 5U;
|
||||
h1 += h0 >> 26; h0 &= 0x3ffffff;
|
||||
h2 += h1 >> 26; h1 &= 0x3ffffff;
|
||||
h3 += h2 >> 26; h2 &= 0x3ffffff;
|
||||
h4 += h3 >> 26; h3 &= 0x3ffffff;
|
||||
|
||||
long c = ((int)(h4 >> 26) - 1) * 5;
|
||||
c += (long)k0 + ((h0) | (h1 << 26));
|
||||
Pack.UInt32_To_LE((uint)c, output, outOff); c >>= 32;
|
||||
c += (long)k1 + ((h1 >> 6) | (h2 << 20));
|
||||
Pack.UInt32_To_LE((uint)c, output, outOff + 4); c >>= 32;
|
||||
c += (long)k2 + ((h2 >> 12) | (h3 << 14));
|
||||
Pack.UInt32_To_LE((uint)c, output, outOff + 8); c >>= 32;
|
||||
c += (long)k3 + ((h3 >> 18) | (h4 << 8));
|
||||
Pack.UInt32_To_LE((uint)c, output, outOff + 12);
|
||||
|
||||
Reset();
|
||||
return BlockSize;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public int DoFinal(Span<byte> output)
|
||||
{
|
||||
Check.OutputLength(output, BlockSize, "output buffer is too short.");
|
||||
|
||||
if (currentBlockOffset > 0)
|
||||
{
|
||||
// Process padded block
|
||||
if (currentBlockOffset < BlockSize)
|
||||
{
|
||||
currentBlock[currentBlockOffset++] = 1;
|
||||
while (currentBlockOffset < BlockSize)
|
||||
{
|
||||
currentBlock[currentBlockOffset++] = 0;
|
||||
}
|
||||
|
||||
h4 -= (1 << 24);
|
||||
}
|
||||
|
||||
ProcessBlock(currentBlock);
|
||||
}
|
||||
|
||||
UnityEngine.Debug.Assert(h4 >> 26 == 0);
|
||||
|
||||
//h0 += (h4 >> 26) * 5U + 5U; h4 &= 0x3ffffff;
|
||||
h0 += 5U;
|
||||
h1 += h0 >> 26; h0 &= 0x3ffffff;
|
||||
h2 += h1 >> 26; h1 &= 0x3ffffff;
|
||||
h3 += h2 >> 26; h2 &= 0x3ffffff;
|
||||
h4 += h3 >> 26; h3 &= 0x3ffffff;
|
||||
|
||||
long c = ((int)(h4 >> 26) - 1) * 5;
|
||||
c += (long)k0 + ((h0) | (h1 << 26));
|
||||
Pack.UInt32_To_LE((uint)c, output); c >>= 32;
|
||||
c += (long)k1 + ((h1 >> 6) | (h2 << 20));
|
||||
Pack.UInt32_To_LE((uint)c, output[4..]); c >>= 32;
|
||||
c += (long)k2 + ((h2 >> 12) | (h3 << 14));
|
||||
Pack.UInt32_To_LE((uint)c, output[8..]); c >>= 32;
|
||||
c += (long)k3 + ((h3 >> 18) | (h4 << 8));
|
||||
Pack.UInt32_To_LE((uint)c, output[12..]);
|
||||
|
||||
Reset();
|
||||
return BlockSize;
|
||||
}
|
||||
#endif
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
currentBlockOffset = 0;
|
||||
|
||||
h0 = h1 = h2 = h3 = h4 = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,560 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER || UNITY_2021_2_OR_NEWER
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005
|
||||
/// </summary>
|
||||
|
||||
|
||||
|
||||
[BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
|
||||
public class FastSalsa20Engine
|
||||
: IStreamCipher
|
||||
{
|
||||
public static readonly int DEFAULT_ROUNDS = 20;
|
||||
|
||||
/** Constants */
|
||||
private const int StateSize = 16; // 16, 32 bit ints = 64 bytes
|
||||
|
||||
private readonly static uint[] TAU_SIGMA = Pack.LE_To_UInt32(Strings.ToAsciiByteArray("expand 16-byte k" + "expand 32-byte k"), 0, 8);
|
||||
|
||||
internal void PackTauOrSigma(int keyLength, uint[] state, int stateOffset)
|
||||
{
|
||||
int tsOff = (keyLength - 16) / 4;
|
||||
state[stateOffset] = TAU_SIGMA[tsOff];
|
||||
state[stateOffset + 1] = TAU_SIGMA[tsOff + 1];
|
||||
state[stateOffset + 2] = TAU_SIGMA[tsOff + 2];
|
||||
state[stateOffset + 3] = TAU_SIGMA[tsOff + 3];
|
||||
}
|
||||
|
||||
protected int rounds;
|
||||
|
||||
/*
|
||||
* variables to hold the state of the engine
|
||||
* during encryption and decryption
|
||||
*/
|
||||
internal int index = 0;
|
||||
internal uint[] engineState = new uint[StateSize]; // state
|
||||
internal uint[] x = new uint[StateSize]; // internal buffer
|
||||
internal byte[] keyStream = new byte[StateSize * 4]; // expanded state, 64 bytes
|
||||
internal bool initialised = false;
|
||||
|
||||
/*
|
||||
* internal counter
|
||||
*/
|
||||
private uint cW0, cW1, cW2;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 20 round Salsa20 engine.
|
||||
/// </summary>
|
||||
public FastSalsa20Engine()
|
||||
: this(DEFAULT_ROUNDS)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Salsa20 engine with a specific number of rounds.
|
||||
/// </summary>
|
||||
/// <param name="rounds">the number of rounds (must be an even number).</param>
|
||||
public FastSalsa20Engine(int rounds)
|
||||
{
|
||||
if (rounds <= 0 || (rounds & 1) != 0)
|
||||
{
|
||||
throw new ArgumentException("'rounds' must be a positive, even number");
|
||||
}
|
||||
|
||||
this.rounds = rounds;
|
||||
}
|
||||
|
||||
public virtual void Init(
|
||||
bool forEncryption,
|
||||
ICipherParameters parameters)
|
||||
{
|
||||
/*
|
||||
* Salsa20 encryption and decryption is completely
|
||||
* symmetrical, so the 'forEncryption' is
|
||||
* irrelevant. (Like 90% of stream ciphers)
|
||||
*/
|
||||
|
||||
ParametersWithIV ivParams = parameters as ParametersWithIV;
|
||||
if (ivParams == null)
|
||||
throw new ArgumentException(AlgorithmName + " Init requires an IV", "parameters");
|
||||
|
||||
byte[] iv = ivParams.GetIV();
|
||||
if (iv == null || iv.Length != NonceSize)
|
||||
throw new ArgumentException(AlgorithmName + " requires exactly " + NonceSize + " bytes of IV");
|
||||
|
||||
ICipherParameters keyParam = ivParams.Parameters;
|
||||
if (keyParam == null)
|
||||
{
|
||||
if (!initialised)
|
||||
throw new InvalidOperationException(AlgorithmName + " KeyParameter can not be null for first initialisation");
|
||||
|
||||
SetKey(null, iv);
|
||||
}
|
||||
else if (keyParam is KeyParameter)
|
||||
{
|
||||
SetKey(((KeyParameter)keyParam).GetKey(), iv);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(AlgorithmName + " Init parameters must contain a KeyParameter (or null for re-init)");
|
||||
}
|
||||
|
||||
Reset();
|
||||
initialised = true;
|
||||
}
|
||||
|
||||
protected virtual int NonceSize
|
||||
{
|
||||
get { return 8; }
|
||||
}
|
||||
|
||||
public virtual string AlgorithmName
|
||||
{
|
||||
get
|
||||
{
|
||||
string name = "Salsa20";
|
||||
if (rounds != DEFAULT_ROUNDS)
|
||||
{
|
||||
name += "/" + rounds;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual byte ReturnByte(
|
||||
byte input)
|
||||
{
|
||||
if (LimitExceeded())
|
||||
{
|
||||
throw new MaxBytesExceededException("2^70 byte limit per IV; Change IV");
|
||||
}
|
||||
|
||||
if (index == 0)
|
||||
{
|
||||
GenerateKeyStream(keyStream);
|
||||
AdvanceCounter();
|
||||
}
|
||||
|
||||
byte output = (byte)(keyStream[index] ^ input);
|
||||
index = (index + 1) & 63;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
protected virtual void AdvanceCounter()
|
||||
{
|
||||
if (++engineState[8] == 0)
|
||||
{
|
||||
++engineState[9];
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe virtual void ProcessBytes(
|
||||
byte[] inBytes,
|
||||
int inOff,
|
||||
int len,
|
||||
byte[] outBytes,
|
||||
int outOff)
|
||||
{
|
||||
if (!initialised)
|
||||
throw new InvalidOperationException(AlgorithmName + " not initialised");
|
||||
|
||||
Check.DataLength(inBytes, inOff, len, "input buffer too short");
|
||||
Check.OutputLength(outBytes, outOff, len, "output buffer too short");
|
||||
|
||||
if (LimitExceeded((uint)len))
|
||||
throw new MaxBytesExceededException("2^70 byte limit per IV would be exceeded; Change IV");
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
GenerateKeyStream(keyStream);
|
||||
AdvanceCounter();
|
||||
|
||||
if (len - i >= 64)
|
||||
{
|
||||
fixed (byte* pbout = outBytes)
|
||||
fixed (byte* pbin = inBytes)
|
||||
fixed (byte* pbkey = keyStream)
|
||||
{
|
||||
#if BESTHTTP_WITH_BURST
|
||||
FastSalsa20EngineHelper.ProcessBytes(pbout, outOff, pbin, inOff, pbkey);
|
||||
#else
|
||||
ulong* pulOut = (ulong*)&pbout[outOff];
|
||||
ulong* pulIn = (ulong*)&pbin[inOff];
|
||||
ulong* pulKeyStream = (ulong*)pbkey;
|
||||
|
||||
pulOut[0] = pulKeyStream[0] ^ pulIn[0];
|
||||
pulOut[1] = pulKeyStream[1] ^ pulIn[1];
|
||||
pulOut[2] = pulKeyStream[2] ^ pulIn[2];
|
||||
pulOut[3] = pulKeyStream[3] ^ pulIn[3];
|
||||
#endif
|
||||
}
|
||||
|
||||
i += 63;
|
||||
index = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
outBytes[i + outOff] = (byte)(keyStream[index] ^ inBytes[i + inOff]);
|
||||
index = (index + 1) & 63;
|
||||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public virtual void ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
|
||||
{
|
||||
if (!initialised)
|
||||
throw new InvalidOperationException(AlgorithmName + " not initialised");
|
||||
|
||||
Check.OutputLength(output, input.Length, "output buffer too short");
|
||||
|
||||
if (LimitExceeded((uint)input.Length))
|
||||
throw new MaxBytesExceededException("2^70 byte limit per IV would be exceeded; Change IV");
|
||||
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
GenerateKeyStream(keyStream);
|
||||
AdvanceCounter();
|
||||
|
||||
if (input.Length - i >= 64)
|
||||
{
|
||||
Span<ulong> lOutput = MemoryMarshal.Cast<byte, ulong>(output.Slice(i));
|
||||
ReadOnlySpan<ulong> lKeyStream = MemoryMarshal.Cast<byte, ulong>(keyStream);
|
||||
ReadOnlySpan<ulong> lInput = MemoryMarshal.Cast<byte, ulong>(input.Slice(i));
|
||||
|
||||
lOutput[0] = lKeyStream[0] ^ lInput[0];
|
||||
lOutput[1] = lKeyStream[1] ^ lInput[1];
|
||||
lOutput[2] = lKeyStream[2] ^ lInput[2];
|
||||
lOutput[3] = lKeyStream[3] ^ lInput[3];
|
||||
|
||||
i += 63;
|
||||
index = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
output[i] = (byte)(keyStream[index++] ^ input[i]);
|
||||
index &= 63;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
index = 0;
|
||||
ResetLimitCounter();
|
||||
ResetCounter();
|
||||
}
|
||||
|
||||
protected virtual void ResetCounter()
|
||||
{
|
||||
engineState[8] = engineState[9] = 0;
|
||||
}
|
||||
|
||||
protected virtual void SetKey(byte[] keyBytes, byte[] ivBytes)
|
||||
{
|
||||
if (keyBytes != null)
|
||||
{
|
||||
if ((keyBytes.Length != 16) && (keyBytes.Length != 32))
|
||||
throw new ArgumentException(AlgorithmName + " requires 128 bit or 256 bit key");
|
||||
|
||||
int tsOff = (keyBytes.Length - 16) / 4;
|
||||
engineState[0] = TAU_SIGMA[tsOff];
|
||||
engineState[5] = TAU_SIGMA[tsOff + 1];
|
||||
engineState[10] = TAU_SIGMA[tsOff + 2];
|
||||
engineState[15] = TAU_SIGMA[tsOff + 3];
|
||||
|
||||
// Key
|
||||
Pack.LE_To_UInt32(keyBytes, 0, engineState, 1, 4);
|
||||
Pack.LE_To_UInt32(keyBytes, keyBytes.Length - 16, engineState, 11, 4);
|
||||
}
|
||||
|
||||
// IV
|
||||
Pack.LE_To_UInt32(ivBytes, 0, engineState, 6, 2);
|
||||
}
|
||||
|
||||
protected virtual void GenerateKeyStream(byte[] output)
|
||||
{
|
||||
SalsaCore(rounds, engineState, x);
|
||||
Pack.UInt32_To_LE(x, output, 0);
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
internal static void SalsaCore(int rounds, ReadOnlySpan<uint> input, Span<uint> output)
|
||||
{
|
||||
if (input.Length < 16)
|
||||
throw new ArgumentException();
|
||||
if (output.Length < 16)
|
||||
throw new ArgumentException();
|
||||
if (rounds % 2 != 0)
|
||||
throw new ArgumentException("Number of rounds must be even");
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
if (Sse41.IsSupported && BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<short>>() == 16)
|
||||
{
|
||||
Vector128<uint> b0, b1, b2, b3;
|
||||
{
|
||||
var I = MemoryMarshal.AsBytes(input[..16]);
|
||||
var t0 = MemoryMarshal.Read<Vector128<short>>(I[0x00..0x10]);
|
||||
var t1 = MemoryMarshal.Read<Vector128<short>>(I[0x10..0x20]);
|
||||
var t2 = MemoryMarshal.Read<Vector128<short>>(I[0x20..0x30]);
|
||||
var t3 = MemoryMarshal.Read<Vector128<short>>(I[0x30..0x40]);
|
||||
|
||||
var u0 = Sse41.Blend(t0, t2, 0xF0);
|
||||
var u1 = Sse41.Blend(t1, t3, 0xC3);
|
||||
var u2 = Sse41.Blend(t0, t2, 0x0F);
|
||||
var u3 = Sse41.Blend(t1, t3, 0x3C);
|
||||
|
||||
b0 = Sse41.Blend(u0, u1, 0xCC).AsUInt32();
|
||||
b1 = Sse41.Blend(u0, u1, 0x33).AsUInt32();
|
||||
b2 = Sse41.Blend(u2, u3, 0xCC).AsUInt32();
|
||||
b3 = Sse41.Blend(u2, u3, 0x33).AsUInt32();
|
||||
}
|
||||
|
||||
var c0 = b0;
|
||||
var c1 = b1;
|
||||
var c2 = b2;
|
||||
var c3 = b3;
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
QuarterRound_Sse2(ref c0, ref c3, ref c2, ref c1);
|
||||
QuarterRound_Sse2(ref c0, ref c1, ref c2, ref c3);
|
||||
}
|
||||
|
||||
b0 = Sse2.Add(b0, c0);
|
||||
b1 = Sse2.Add(b1, c1);
|
||||
b2 = Sse2.Add(b2, c2);
|
||||
b3 = Sse2.Add(b3, c3);
|
||||
|
||||
{
|
||||
var t0 = b0.AsUInt16();
|
||||
var t1 = b1.AsUInt16();
|
||||
var t2 = b2.AsUInt16();
|
||||
var t3 = b3.AsUInt16();
|
||||
|
||||
var u0 = Sse41.Blend(t0, t1, 0xCC);
|
||||
var u1 = Sse41.Blend(t0, t1, 0x33);
|
||||
var u2 = Sse41.Blend(t2, t3, 0xCC);
|
||||
var u3 = Sse41.Blend(t2, t3, 0x33);
|
||||
|
||||
var v0 = Sse41.Blend(u0, u2, 0xF0);
|
||||
var v1 = Sse41.Blend(u1, u3, 0xC3);
|
||||
var v2 = Sse41.Blend(u0, u2, 0x0F);
|
||||
var v3 = Sse41.Blend(u1, u3, 0x3C);
|
||||
|
||||
var X = MemoryMarshal.AsBytes(output[..16]);
|
||||
MemoryMarshal.Write(X[0x00..0x10], ref v0);
|
||||
MemoryMarshal.Write(X[0x10..0x20], ref v1);
|
||||
MemoryMarshal.Write(X[0x20..0x30], ref v2);
|
||||
MemoryMarshal.Write(X[0x30..0x40], ref v3);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint x00 = input[ 0];
|
||||
uint x01 = input[ 1];
|
||||
uint x02 = input[ 2];
|
||||
uint x03 = input[ 3];
|
||||
uint x04 = input[ 4];
|
||||
uint x05 = input[ 5];
|
||||
uint x06 = input[ 6];
|
||||
uint x07 = input[ 7];
|
||||
uint x08 = input[ 8];
|
||||
uint x09 = input[ 9];
|
||||
uint x10 = input[10];
|
||||
uint x11 = input[11];
|
||||
uint x12 = input[12];
|
||||
uint x13 = input[13];
|
||||
uint x14 = input[14];
|
||||
uint x15 = input[15];
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
QuarterRound(ref x00, ref x04, ref x08, ref x12);
|
||||
QuarterRound(ref x05, ref x09, ref x13, ref x01);
|
||||
QuarterRound(ref x10, ref x14, ref x02, ref x06);
|
||||
QuarterRound(ref x15, ref x03, ref x07, ref x11);
|
||||
|
||||
QuarterRound(ref x00, ref x01, ref x02, ref x03);
|
||||
QuarterRound(ref x05, ref x06, ref x07, ref x04);
|
||||
QuarterRound(ref x10, ref x11, ref x08, ref x09);
|
||||
QuarterRound(ref x15, ref x12, ref x13, ref x14);
|
||||
}
|
||||
|
||||
output[ 0] = x00 + input[ 0];
|
||||
output[ 1] = x01 + input[ 1];
|
||||
output[ 2] = x02 + input[ 2];
|
||||
output[ 3] = x03 + input[ 3];
|
||||
output[ 4] = x04 + input[ 4];
|
||||
output[ 5] = x05 + input[ 5];
|
||||
output[ 6] = x06 + input[ 6];
|
||||
output[ 7] = x07 + input[ 7];
|
||||
output[ 8] = x08 + input[ 8];
|
||||
output[ 9] = x09 + input[ 9];
|
||||
output[10] = x10 + input[10];
|
||||
output[11] = x11 + input[11];
|
||||
output[12] = x12 + input[12];
|
||||
output[13] = x13 + input[13];
|
||||
output[14] = x14 + input[14];
|
||||
output[15] = x15 + input[15];
|
||||
}
|
||||
#else
|
||||
internal static void SalsaCore(int rounds, uint[] input, uint[] output)
|
||||
{
|
||||
if (input.Length < 16)
|
||||
throw new ArgumentException();
|
||||
if (output.Length < 16)
|
||||
throw new ArgumentException();
|
||||
if (rounds % 2 != 0)
|
||||
throw new ArgumentException("Number of rounds must be even");
|
||||
|
||||
uint x00 = input[0];
|
||||
uint x01 = input[1];
|
||||
uint x02 = input[2];
|
||||
uint x03 = input[3];
|
||||
uint x04 = input[4];
|
||||
uint x05 = input[5];
|
||||
uint x06 = input[6];
|
||||
uint x07 = input[7];
|
||||
uint x08 = input[8];
|
||||
uint x09 = input[9];
|
||||
uint x10 = input[10];
|
||||
uint x11 = input[11];
|
||||
uint x12 = input[12];
|
||||
uint x13 = input[13];
|
||||
uint x14 = input[14];
|
||||
uint x15 = input[15];
|
||||
|
||||
for (int i = rounds; i > 0; i -= 2)
|
||||
{
|
||||
QuarterRound(ref x00, ref x04, ref x08, ref x12);
|
||||
QuarterRound(ref x05, ref x09, ref x13, ref x01);
|
||||
QuarterRound(ref x10, ref x14, ref x02, ref x06);
|
||||
QuarterRound(ref x15, ref x03, ref x07, ref x11);
|
||||
|
||||
QuarterRound(ref x00, ref x01, ref x02, ref x03);
|
||||
QuarterRound(ref x05, ref x06, ref x07, ref x04);
|
||||
QuarterRound(ref x10, ref x11, ref x08, ref x09);
|
||||
QuarterRound(ref x15, ref x12, ref x13, ref x14);
|
||||
}
|
||||
|
||||
output[ 0] = x00 + input[ 0];
|
||||
output[ 1] = x01 + input[ 1];
|
||||
output[ 2] = x02 + input[ 2];
|
||||
output[ 3] = x03 + input[ 3];
|
||||
output[ 4] = x04 + input[ 4];
|
||||
output[ 5] = x05 + input[ 5];
|
||||
output[ 6] = x06 + input[ 6];
|
||||
output[ 7] = x07 + input[ 7];
|
||||
output[ 8] = x08 + input[ 8];
|
||||
output[ 9] = x09 + input[ 9];
|
||||
output[10] = x10 + input[10];
|
||||
output[11] = x11 + input[11];
|
||||
output[12] = x12 + input[12];
|
||||
output[13] = x13 + input[13];
|
||||
output[14] = x14 + input[14];
|
||||
output[15] = x15 + input[15];
|
||||
}
|
||||
#endif
|
||||
|
||||
internal void ResetLimitCounter()
|
||||
{
|
||||
cW0 = 0;
|
||||
cW1 = 0;
|
||||
cW2 = 0;
|
||||
}
|
||||
|
||||
internal bool LimitExceeded()
|
||||
{
|
||||
if (++cW0 == 0)
|
||||
{
|
||||
if (++cW1 == 0)
|
||||
{
|
||||
return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6)
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* this relies on the fact len will always be positive.
|
||||
*/
|
||||
internal bool LimitExceeded(
|
||||
uint len)
|
||||
{
|
||||
uint old = cW0;
|
||||
cW0 += len;
|
||||
if (cW0 < old)
|
||||
{
|
||||
if (++cW1 == 0)
|
||||
{
|
||||
return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6)
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER || UNITY_2021_2_OR_NEWER
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
private static void QuarterRound(ref uint a, ref uint b, ref uint c, ref uint d)
|
||||
{
|
||||
b ^= Integers.RotateLeft(a + d, 7);
|
||||
c ^= Integers.RotateLeft(b + a, 9);
|
||||
d ^= Integers.RotateLeft(c + b, 13);
|
||||
a ^= Integers.RotateLeft(d + c, 18);
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void QuarterRound_Sse2(ref Vector128<uint> a, ref Vector128<uint> b, ref Vector128<uint> c,
|
||||
ref Vector128<uint> d)
|
||||
{
|
||||
b = Sse2.Xor(b, Rotate_Sse2(Sse2.Add(a, d), 7));
|
||||
c = Sse2.Xor(c, Rotate_Sse2(Sse2.Add(b, a), 9));
|
||||
d = Sse2.Xor(d, Rotate_Sse2(Sse2.Add(c, b), 13));
|
||||
a = Sse2.Xor(a, Rotate_Sse2(Sse2.Add(d, c), 18));
|
||||
|
||||
b = Sse2.Shuffle(b, 0x93);
|
||||
c = Sse2.Shuffle(c, 0x4E);
|
||||
d = Sse2.Shuffle(d, 0x39);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector128<uint> Rotate_Sse2(Vector128<uint> x, byte sl)
|
||||
{
|
||||
byte sr = (byte)(32 - sl);
|
||||
return Sse2.Xor(Sse2.ShiftLeftLogical(x, sl), Sse2.ShiftRightLogical(x, sr));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
using System;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
#if BESTHTTP_WITH_BURST
|
||||
[Unity.Burst.BurstCompile]
|
||||
#endif
|
||||
internal static class FastSalsa20EngineHelper
|
||||
{
|
||||
#if BESTHTTP_WITH_BURST
|
||||
[Unity.Burst.BurstCompile]
|
||||
public unsafe static void ProcessBytes([Unity.Burst.NoAlias] byte* outBytes, int outOff, [Unity.Burst.NoAlias] byte* inBytes, int inOff, [Unity.Burst.NoAlias] byte* keyStream)
|
||||
{
|
||||
//for (int i = 0; i < 64; ++i)
|
||||
// outBytes[idx + i + outOff] = (byte)(keyStream[i] ^ inBytes[idx + i + inOff]);
|
||||
|
||||
ulong* pulOut = (ulong*)&outBytes[outOff];
|
||||
ulong* pulIn = (ulong*)&inBytes[inOff];
|
||||
ulong* pulKeyStream = (ulong*)keyStream;
|
||||
|
||||
pulOut[0] = pulKeyStream[0] ^ pulIn[0];
|
||||
pulOut[1] = pulKeyStream[1] ^ pulIn[1];
|
||||
pulOut[2] = pulKeyStream[2] ^ pulIn[2];
|
||||
pulOut[3] = pulKeyStream[3] ^ pulIn[3];
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Modes;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Math;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
/**
|
||||
* Implements the Segmented Integer Counter (SIC) mode on top of a simple
|
||||
* block cipher.
|
||||
*/
|
||||
public class FastSicBlockCipher
|
||||
: IBlockCipherMode
|
||||
{
|
||||
private readonly IBlockCipher cipher;
|
||||
private readonly int blockSize;
|
||||
private readonly byte[] counter;
|
||||
private readonly byte[] counterOut;
|
||||
private byte[] IV;
|
||||
|
||||
/**
|
||||
* Basic constructor.
|
||||
*
|
||||
* @param c the block cipher to be used.
|
||||
*/
|
||||
public FastSicBlockCipher(IBlockCipher cipher)
|
||||
{
|
||||
this.cipher = cipher;
|
||||
this.blockSize = cipher.GetBlockSize();
|
||||
this.counter = new byte[blockSize];
|
||||
this.counterOut = new byte[blockSize];
|
||||
this.IV = new byte[blockSize];
|
||||
}
|
||||
|
||||
/**
|
||||
* return the underlying block cipher that we are wrapping.
|
||||
*
|
||||
* @return the underlying block cipher that we are wrapping.
|
||||
*/
|
||||
public IBlockCipher UnderlyingCipher => cipher;
|
||||
|
||||
public virtual void Init(
|
||||
bool forEncryption, //ignored by this CTR mode
|
||||
ICipherParameters parameters)
|
||||
{
|
||||
ParametersWithIV ivParam = parameters as ParametersWithIV;
|
||||
if (ivParam == null)
|
||||
throw new ArgumentException("CTR/SIC mode requires ParametersWithIV", "parameters");
|
||||
|
||||
this.IV = Arrays.Clone(ivParam.GetIV());
|
||||
|
||||
if (blockSize < IV.Length)
|
||||
throw new ArgumentException("CTR/SIC mode requires IV no greater than: " + blockSize + " bytes.");
|
||||
|
||||
int maxCounterSize = System.Math.Min(8, blockSize / 2);
|
||||
if (blockSize - IV.Length > maxCounterSize)
|
||||
throw new ArgumentException("CTR/SIC mode requires IV of at least: " + (blockSize - maxCounterSize) + " bytes.");
|
||||
|
||||
// if null it's an IV changed only.
|
||||
if (ivParam.Parameters != null)
|
||||
{
|
||||
cipher.Init(true, ivParam.Parameters);
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public virtual string AlgorithmName
|
||||
{
|
||||
get { return cipher.AlgorithmName + "/SIC"; }
|
||||
}
|
||||
|
||||
public virtual bool IsPartialBlockOkay
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public virtual int GetBlockSize()
|
||||
{
|
||||
return cipher.GetBlockSize();
|
||||
}
|
||||
|
||||
public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
|
||||
{
|
||||
cipher.ProcessBlock(counter, 0, counterOut, 0);
|
||||
|
||||
//
|
||||
// XOR the counterOut with the plaintext producing the cipher text
|
||||
//
|
||||
for (int i = 0; i < counterOut.Length; i++)
|
||||
{
|
||||
output[outOff + i] = (byte)(counterOut[i] ^ input[inOff + i]);
|
||||
}
|
||||
|
||||
// Increment the counter
|
||||
int j = counter.Length;
|
||||
while (--j >= 0 && ++counter[j] == 0)
|
||||
{
|
||||
}
|
||||
|
||||
return counter.Length;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
|
||||
{
|
||||
cipher.ProcessBlock(counter, 0, counterOut, 0);
|
||||
|
||||
//
|
||||
// XOR the counterOut with the plaintext producing the cipher text
|
||||
//
|
||||
for (int i = 0; i < counterOut.Length; i++)
|
||||
{
|
||||
output[i] = (byte)(counterOut[i] ^ input[i]);
|
||||
}
|
||||
|
||||
// Increment the counter
|
||||
int j = counter.Length;
|
||||
while (--j >= 0 && ++counter[j] == 0)
|
||||
{
|
||||
}
|
||||
|
||||
return counter.Length;
|
||||
}
|
||||
#endif
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
Arrays.Fill(counter, (byte)0);
|
||||
Array.Copy(IV, 0, counter, 0, IV.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,499 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
using System.IO;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto.Impl;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
/// <summary>A generic TLS 1.2 AEAD cipher.</summary>
|
||||
|
||||
|
||||
|
||||
[BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
|
||||
public sealed class FastTlsAeadCipher
|
||||
: TlsCipher
|
||||
{
|
||||
public const int AEAD_CCM = 1;
|
||||
public const int AEAD_CHACHA20_POLY1305 = 2;
|
||||
public const int AEAD_GCM = 3;
|
||||
|
||||
private const int NONCE_RFC5288 = 1;
|
||||
private const int NONCE_RFC7905 = 2;
|
||||
|
||||
private readonly TlsCryptoParameters m_cryptoParams;
|
||||
private readonly int m_keySize;
|
||||
private readonly int m_macSize;
|
||||
private readonly int m_fixed_iv_length;
|
||||
private readonly int m_record_iv_length;
|
||||
|
||||
private readonly TlsAeadCipherImpl m_decryptCipher, m_encryptCipher;
|
||||
private readonly byte[] m_decryptNonce, m_encryptNonce;
|
||||
|
||||
private readonly bool m_isTlsV13;
|
||||
private readonly int m_nonceMode;
|
||||
|
||||
/// <exception cref="IOException"/>
|
||||
public FastTlsAeadCipher(TlsCryptoParameters cryptoParams, TlsAeadCipherImpl encryptCipher,
|
||||
TlsAeadCipherImpl decryptCipher, int keySize, int macSize, int aeadType)
|
||||
{
|
||||
SecurityParameters securityParameters = cryptoParams.SecurityParameters;
|
||||
ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
|
||||
|
||||
if (!TlsImplUtilities.IsTlsV12(negotiatedVersion))
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
|
||||
this.m_isTlsV13 = TlsImplUtilities.IsTlsV13(negotiatedVersion);
|
||||
this.m_nonceMode = GetNonceMode(m_isTlsV13, aeadType);
|
||||
|
||||
switch (m_nonceMode)
|
||||
{
|
||||
case NONCE_RFC5288:
|
||||
this.m_fixed_iv_length = 4;
|
||||
this.m_record_iv_length = 8;
|
||||
break;
|
||||
case NONCE_RFC7905:
|
||||
this.m_fixed_iv_length = 12;
|
||||
this.m_record_iv_length = 0;
|
||||
break;
|
||||
default:
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
}
|
||||
|
||||
this.m_cryptoParams = cryptoParams;
|
||||
this.m_keySize = keySize;
|
||||
this.m_macSize = macSize;
|
||||
|
||||
this.m_decryptCipher = decryptCipher;
|
||||
this.m_encryptCipher = encryptCipher;
|
||||
|
||||
this.m_decryptNonce = new byte[m_fixed_iv_length];
|
||||
this.m_encryptNonce = new byte[m_fixed_iv_length];
|
||||
|
||||
bool isServer = cryptoParams.IsServer;
|
||||
if (m_isTlsV13)
|
||||
{
|
||||
RekeyCipher(securityParameters, decryptCipher, m_decryptNonce, !isServer);
|
||||
RekeyCipher(securityParameters, encryptCipher, m_encryptNonce, isServer);
|
||||
return;
|
||||
}
|
||||
|
||||
int keyBlockSize = (2 * keySize) + (2 * m_fixed_iv_length);
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
Span<byte> keyBlock = keyBlockSize <= 512
|
||||
? stackalloc byte[keyBlockSize]
|
||||
: new byte[keyBlockSize];
|
||||
TlsImplUtilities.CalculateKeyBlock(cryptoParams, keyBlock);
|
||||
|
||||
if (isServer)
|
||||
{
|
||||
decryptCipher.SetKey(keyBlock[..keySize]); keyBlock = keyBlock[keySize..];
|
||||
encryptCipher.SetKey(keyBlock[..keySize]); keyBlock = keyBlock[keySize..];
|
||||
|
||||
keyBlock[..m_fixed_iv_length].CopyTo(m_decryptNonce); keyBlock = keyBlock[m_fixed_iv_length..];
|
||||
keyBlock[..m_fixed_iv_length].CopyTo(m_encryptNonce); keyBlock = keyBlock[m_fixed_iv_length..];
|
||||
}
|
||||
else
|
||||
{
|
||||
encryptCipher.SetKey(keyBlock[..keySize]); keyBlock = keyBlock[keySize..];
|
||||
decryptCipher.SetKey(keyBlock[..keySize]); keyBlock = keyBlock[keySize..];
|
||||
|
||||
keyBlock[..m_fixed_iv_length].CopyTo(m_encryptNonce); keyBlock = keyBlock[m_fixed_iv_length..];
|
||||
keyBlock[..m_fixed_iv_length].CopyTo(m_decryptNonce); keyBlock = keyBlock[m_fixed_iv_length..];
|
||||
}
|
||||
|
||||
if (!keyBlock.IsEmpty)
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
#else
|
||||
byte[] keyBlock = TlsImplUtilities.CalculateKeyBlock(cryptoParams, keyBlockSize);
|
||||
int pos = 0;
|
||||
|
||||
if (isServer)
|
||||
{
|
||||
decryptCipher.SetKey(keyBlock, pos, keySize); pos += keySize;
|
||||
encryptCipher.SetKey(keyBlock, pos, keySize); pos += keySize;
|
||||
|
||||
Array.Copy(keyBlock, pos, m_decryptNonce, 0, m_fixed_iv_length); pos += m_fixed_iv_length;
|
||||
Array.Copy(keyBlock, pos, m_encryptNonce, 0, m_fixed_iv_length); pos += m_fixed_iv_length;
|
||||
}
|
||||
else
|
||||
{
|
||||
encryptCipher.SetKey(keyBlock, pos, keySize); pos += keySize;
|
||||
decryptCipher.SetKey(keyBlock, pos, keySize); pos += keySize;
|
||||
|
||||
Array.Copy(keyBlock, pos, m_encryptNonce, 0, m_fixed_iv_length); pos += m_fixed_iv_length;
|
||||
Array.Copy(keyBlock, pos, m_decryptNonce, 0, m_fixed_iv_length); pos += m_fixed_iv_length;
|
||||
}
|
||||
|
||||
if (pos != keyBlockSize)
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
#endif
|
||||
|
||||
int nonceLength = m_fixed_iv_length + m_record_iv_length;
|
||||
|
||||
// NOTE: Ensure dummy nonce is not part of the generated sequence(s)
|
||||
byte[] dummyNonce = new byte[nonceLength];
|
||||
dummyNonce[0] = (byte)~m_encryptNonce[0];
|
||||
dummyNonce[1] = (byte)~m_decryptNonce[1];
|
||||
|
||||
encryptCipher.Init(dummyNonce, macSize, null);
|
||||
decryptCipher.Init(dummyNonce, macSize, null);
|
||||
}
|
||||
|
||||
public int GetCiphertextDecodeLimit(int plaintextLimit)
|
||||
{
|
||||
return plaintextLimit + m_macSize + m_record_iv_length + (m_isTlsV13 ? 1 : 0);
|
||||
}
|
||||
|
||||
public int GetCiphertextEncodeLimit(int plaintextLength, int plaintextLimit)
|
||||
{
|
||||
int innerPlaintextLimit = plaintextLength;
|
||||
if (m_isTlsV13)
|
||||
{
|
||||
// TODO[tls13] Add support for padding
|
||||
int maxPadding = 0;
|
||||
|
||||
innerPlaintextLimit = 1 + System.Math.Min(plaintextLimit, plaintextLength + maxPadding);
|
||||
}
|
||||
|
||||
return innerPlaintextLimit + m_macSize + m_record_iv_length;
|
||||
}
|
||||
|
||||
public int GetPlaintextLimit(int ciphertextLimit)
|
||||
{
|
||||
return ciphertextLimit - m_macSize - m_record_iv_length - (m_isTlsV13 ? 1 : 0);
|
||||
}
|
||||
|
||||
public TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
|
||||
int headerAllocation, byte[] plaintext, int plaintextOffset, int plaintextLength)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
return EncodePlaintext(seqNo, contentType, recordVersion, headerAllocation,
|
||||
plaintext.AsSpan(plaintextOffset, plaintextLength));
|
||||
#else
|
||||
byte[] nonce = new byte[m_encryptNonce.Length + m_record_iv_length];
|
||||
|
||||
switch (m_nonceMode)
|
||||
{
|
||||
case NONCE_RFC5288:
|
||||
Array.Copy(m_encryptNonce, 0, nonce, 0, m_encryptNonce.Length);
|
||||
// RFC 5288/6655: The nonce_explicit MAY be the 64-bit sequence number.
|
||||
TlsUtilities.WriteUint64(seqNo, nonce, m_encryptNonce.Length);
|
||||
break;
|
||||
case NONCE_RFC7905:
|
||||
TlsUtilities.WriteUint64(seqNo, nonce, nonce.Length - 8);
|
||||
for (int i = 0; i < m_encryptNonce.Length; ++i)
|
||||
{
|
||||
nonce[i] ^= m_encryptNonce[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
}
|
||||
|
||||
int extraLength = m_isTlsV13 ? 1 : 0;
|
||||
|
||||
// TODO[tls13] If we support adding padding to TLSInnerPlaintext, this will need review
|
||||
int encryptionLength = m_encryptCipher.GetOutputSize(plaintextLength + extraLength);
|
||||
int ciphertextLength = m_record_iv_length + encryptionLength;
|
||||
|
||||
byte[] output = BufferPool.Get(headerAllocation + ciphertextLength, true); //new byte[headerAllocation + ciphertextLength];
|
||||
int outputPos = headerAllocation;
|
||||
|
||||
if (m_record_iv_length != 0)
|
||||
{
|
||||
Array.Copy(nonce, nonce.Length - m_record_iv_length, output, outputPos, m_record_iv_length);
|
||||
outputPos += m_record_iv_length;
|
||||
}
|
||||
|
||||
short recordType = m_isTlsV13 ? ContentType.application_data : contentType;
|
||||
|
||||
byte[] additionalData = GetAdditionalData(seqNo, recordType, recordVersion, ciphertextLength,
|
||||
plaintextLength);
|
||||
|
||||
try
|
||||
{
|
||||
Array.Copy(plaintext, plaintextOffset, output, outputPos, plaintextLength);
|
||||
if (m_isTlsV13)
|
||||
{
|
||||
output[outputPos + plaintextLength] = (byte)contentType;
|
||||
}
|
||||
|
||||
m_encryptCipher.Init(nonce, m_macSize, additionalData);
|
||||
outputPos += m_encryptCipher.DoFinal(output, outputPos, plaintextLength + extraLength, output,
|
||||
outputPos);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error, e);
|
||||
}
|
||||
|
||||
if (outputPos != headerAllocation + ciphertextLength)
|
||||
{
|
||||
// NOTE: The additional data mechanism for AEAD ciphers requires exact output size prediction.
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
}
|
||||
|
||||
return new TlsEncodeResult(output, 0, outputPos, recordType, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
|
||||
int headerAllocation, ReadOnlySpan<byte> plaintext)
|
||||
{
|
||||
byte[] nonce = new byte[m_encryptNonce.Length + m_record_iv_length];
|
||||
|
||||
switch (m_nonceMode)
|
||||
{
|
||||
case NONCE_RFC5288:
|
||||
Array.Copy(m_encryptNonce, 0, nonce, 0, m_encryptNonce.Length);
|
||||
// RFC 5288/6655: The nonce_explicit MAY be the 64-bit sequence number.
|
||||
TlsUtilities.WriteUint64(seqNo, nonce, m_encryptNonce.Length);
|
||||
break;
|
||||
case NONCE_RFC7905:
|
||||
TlsUtilities.WriteUint64(seqNo, nonce, nonce.Length - 8);
|
||||
for (int i = 0; i < m_encryptNonce.Length; ++i)
|
||||
{
|
||||
nonce[i] ^= m_encryptNonce[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
}
|
||||
|
||||
int extraLength = m_isTlsV13 ? 1 : 0;
|
||||
|
||||
// TODO[tls13] If we support adding padding to TLSInnerPlaintext, this will need review
|
||||
int encryptionLength = m_encryptCipher.GetOutputSize(plaintext.Length + extraLength);
|
||||
int ciphertextLength = m_record_iv_length + encryptionLength;
|
||||
|
||||
byte[] output = new byte[headerAllocation + ciphertextLength];
|
||||
int outputPos = headerAllocation;
|
||||
|
||||
if (m_record_iv_length != 0)
|
||||
{
|
||||
Array.Copy(nonce, nonce.Length - m_record_iv_length, output, outputPos, m_record_iv_length);
|
||||
outputPos += m_record_iv_length;
|
||||
}
|
||||
|
||||
short recordType = m_isTlsV13 ? ContentType.application_data : contentType;
|
||||
|
||||
byte[] additionalData = GetAdditionalData(seqNo, recordType, recordVersion, ciphertextLength,
|
||||
plaintext.Length);
|
||||
|
||||
try
|
||||
{
|
||||
plaintext.CopyTo(output.AsSpan(outputPos));
|
||||
if (m_isTlsV13)
|
||||
{
|
||||
output[outputPos + plaintext.Length] = (byte)contentType;
|
||||
}
|
||||
|
||||
m_encryptCipher.Init(nonce, m_macSize, additionalData);
|
||||
outputPos += m_encryptCipher.DoFinal(output, outputPos, plaintext.Length + extraLength, output,
|
||||
outputPos);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error, e);
|
||||
}
|
||||
|
||||
if (outputPos != output.Length)
|
||||
{
|
||||
// NOTE: The additional data mechanism for AEAD ciphers requires exact output size prediction.
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
}
|
||||
|
||||
return new TlsEncodeResult(output, 0, output.Length, recordType);
|
||||
}
|
||||
#endif
|
||||
|
||||
public TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
|
||||
byte[] ciphertext, int ciphertextOffset, int ciphertextLength)
|
||||
{
|
||||
if (GetPlaintextLimit(ciphertextLength) < 0)
|
||||
throw new TlsFatalAlert(AlertDescription.decode_error);
|
||||
|
||||
byte[] nonce = new byte[m_decryptNonce.Length + m_record_iv_length];
|
||||
|
||||
switch (m_nonceMode)
|
||||
{
|
||||
case NONCE_RFC5288:
|
||||
Array.Copy(m_decryptNonce, 0, nonce, 0, m_decryptNonce.Length);
|
||||
Array.Copy(ciphertext, ciphertextOffset, nonce, nonce.Length - m_record_iv_length,
|
||||
m_record_iv_length);
|
||||
break;
|
||||
case NONCE_RFC7905:
|
||||
TlsUtilities.WriteUint64(seqNo, nonce, nonce.Length - 8);
|
||||
for (int i = 0; i < m_decryptNonce.Length; ++i)
|
||||
{
|
||||
nonce[i] ^= m_decryptNonce[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
}
|
||||
|
||||
int encryptionOffset = ciphertextOffset + m_record_iv_length;
|
||||
int encryptionLength = ciphertextLength - m_record_iv_length;
|
||||
int plaintextLength = m_decryptCipher.GetOutputSize(encryptionLength);
|
||||
|
||||
byte[] additionalData = GetAdditionalData(seqNo, recordType, recordVersion, ciphertextLength,
|
||||
plaintextLength);
|
||||
|
||||
int outputPos;
|
||||
try
|
||||
{
|
||||
m_decryptCipher.Init(nonce, m_macSize, additionalData);
|
||||
outputPos = m_decryptCipher.DoFinal(ciphertext, encryptionOffset, encryptionLength, ciphertext,
|
||||
encryptionOffset);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new TlsFatalAlert(AlertDescription.bad_record_mac, e);
|
||||
}
|
||||
|
||||
if (outputPos != plaintextLength)
|
||||
{
|
||||
// NOTE: The additional data mechanism for AEAD ciphers requires exact output size prediction.
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
}
|
||||
|
||||
short contentType = recordType;
|
||||
if (m_isTlsV13)
|
||||
{
|
||||
// Strip padding and read true content type from TLSInnerPlaintext
|
||||
int pos = plaintextLength;
|
||||
for (; ; )
|
||||
{
|
||||
if (--pos < 0)
|
||||
throw new TlsFatalAlert(AlertDescription.unexpected_message);
|
||||
|
||||
byte octet = ciphertext[encryptionOffset + pos];
|
||||
if (0 != octet)
|
||||
{
|
||||
contentType = (short)(octet & 0xFF);
|
||||
plaintextLength = pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new TlsDecodeResult(ciphertext, encryptionOffset, plaintextLength, contentType);
|
||||
}
|
||||
|
||||
public void RekeyDecoder()
|
||||
{
|
||||
RekeyCipher(m_cryptoParams.SecurityParameters, m_decryptCipher, m_decryptNonce, !m_cryptoParams.IsServer);
|
||||
}
|
||||
|
||||
public void RekeyEncoder()
|
||||
{
|
||||
RekeyCipher(m_cryptoParams.SecurityParameters, m_encryptCipher, m_encryptNonce, m_cryptoParams.IsServer);
|
||||
}
|
||||
|
||||
public bool UsesOpaqueRecordType
|
||||
{
|
||||
get { return m_isTlsV13; }
|
||||
}
|
||||
|
||||
private byte[] GetAdditionalData(long seqNo, short recordType, ProtocolVersion recordVersion,
|
||||
int ciphertextLength, int plaintextLength)
|
||||
{
|
||||
if (m_isTlsV13)
|
||||
{
|
||||
/*
|
||||
* TLSCiphertext.opaque_type || TLSCiphertext.legacy_record_version || TLSCiphertext.length
|
||||
*/
|
||||
byte[] additional_data = new byte[5];
|
||||
|
||||
TlsUtilities.WriteUint8(recordType, additional_data, 0);
|
||||
TlsUtilities.WriteVersion(recordVersion, additional_data, 1);
|
||||
TlsUtilities.WriteUint16(ciphertextLength, additional_data, 3);
|
||||
return additional_data;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length
|
||||
*/
|
||||
byte[] additional_data = new byte[13];
|
||||
|
||||
TlsUtilities.WriteUint64(seqNo, additional_data, 0);
|
||||
TlsUtilities.WriteUint8(recordType, additional_data, 8);
|
||||
TlsUtilities.WriteVersion(recordVersion, additional_data, 9);
|
||||
TlsUtilities.WriteUint16(plaintextLength, additional_data, 11);
|
||||
return additional_data;
|
||||
}
|
||||
}
|
||||
|
||||
private void RekeyCipher(SecurityParameters securityParameters, TlsAeadCipherImpl cipher,
|
||||
byte[] nonce, bool serverSecret)
|
||||
{
|
||||
if (!m_isTlsV13)
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
|
||||
TlsSecret secret = serverSecret
|
||||
? securityParameters.TrafficSecretServer
|
||||
: securityParameters.TrafficSecretClient;
|
||||
|
||||
// TODO[tls13] For early data, have to disable server->client
|
||||
if (null == secret)
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
|
||||
Setup13Cipher(cipher, nonce, secret, securityParameters.PrfCryptoHashAlgorithm);
|
||||
}
|
||||
|
||||
private void Setup13Cipher(TlsAeadCipherImpl cipher, byte[] nonce, TlsSecret secret,
|
||||
int cryptoHashAlgorithm)
|
||||
{
|
||||
byte[] key = TlsCryptoUtilities.HkdfExpandLabel(secret, cryptoHashAlgorithm, "key",
|
||||
TlsUtilities.EmptyBytes, m_keySize).Extract();
|
||||
byte[] iv = TlsCryptoUtilities.HkdfExpandLabel(secret, cryptoHashAlgorithm, "iv", TlsUtilities.EmptyBytes,
|
||||
m_fixed_iv_length).Extract();
|
||||
|
||||
cipher.SetKey(key, 0, m_keySize);
|
||||
Array.Copy(iv, 0, nonce, 0, m_fixed_iv_length);
|
||||
|
||||
// NOTE: Ensure dummy nonce is not part of the generated sequence(s)
|
||||
iv[0] ^= 0x80;
|
||||
cipher.Init(iv, m_macSize, null);
|
||||
}
|
||||
|
||||
private static int GetNonceMode(bool isTLSv13, int aeadType)
|
||||
{
|
||||
switch (aeadType)
|
||||
{
|
||||
case AEAD_CCM:
|
||||
case AEAD_GCM:
|
||||
return isTLSv13 ? NONCE_RFC7905 : NONCE_RFC5288;
|
||||
|
||||
case AEAD_CHACHA20_POLY1305:
|
||||
return NONCE_RFC7905;
|
||||
|
||||
default:
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Modes;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto.Impl;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
//public sealed class NoCopyKeyParameter
|
||||
// : ICipherParameters
|
||||
//{
|
||||
// private readonly byte[] key;
|
||||
//
|
||||
// public NoCopyKeyParameter(byte[] key)
|
||||
// :this(key, 0, key.Length)
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// public NoCopyKeyParameter(
|
||||
// byte[] key,
|
||||
// int keyOff,
|
||||
// int keyLen)
|
||||
// {
|
||||
// if (key == null)
|
||||
// throw new ArgumentNullException("key");
|
||||
// if (keyOff < 0 || keyOff > key.Length)
|
||||
// throw new ArgumentOutOfRangeException("keyOff");
|
||||
// if (keyLen < 0 || keyLen > (key.Length - keyOff))
|
||||
// throw new ArgumentOutOfRangeException("keyLen");
|
||||
//
|
||||
// this.key = new byte[keyLen];
|
||||
// Array.Copy(key, keyOff, this.key, 0, keyLen);
|
||||
// }
|
||||
//
|
||||
// public byte[] GetKey()
|
||||
// {
|
||||
// return key;// (byte[])key.Clone();
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//public sealed class FastAeadParameters
|
||||
// : ICipherParameters
|
||||
//{
|
||||
// private readonly byte[] associatedText;
|
||||
// private readonly byte[] nonce;
|
||||
// private readonly NoCopyKeyParameter key;
|
||||
// private readonly int macSize;
|
||||
//
|
||||
// /**
|
||||
// * Base constructor.
|
||||
// *
|
||||
// * @param key key to be used by underlying cipher
|
||||
// * @param macSize macSize in bits
|
||||
// * @param nonce nonce to be used
|
||||
// */
|
||||
// public FastAeadParameters(NoCopyKeyParameter key, int macSize, byte[] nonce)
|
||||
// : this(key, macSize, nonce, null)
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Base constructor.
|
||||
// *
|
||||
// * @param key key to be used by underlying cipher
|
||||
// * @param macSize macSize in bits
|
||||
// * @param nonce nonce to be used
|
||||
// * @param associatedText associated text, if any
|
||||
// */
|
||||
// public FastAeadParameters(
|
||||
// NoCopyKeyParameter key,
|
||||
// int macSize,
|
||||
// byte[] nonce,
|
||||
// byte[] associatedText)
|
||||
// {
|
||||
// this.key = key;
|
||||
// this.nonce = nonce;
|
||||
// this.macSize = macSize;
|
||||
// this.associatedText = associatedText;
|
||||
// }
|
||||
//
|
||||
// public NoCopyKeyParameter Key
|
||||
// {
|
||||
// get { return key; }
|
||||
// }
|
||||
//
|
||||
// public int MacSize
|
||||
// {
|
||||
// get { return macSize; }
|
||||
// }
|
||||
//
|
||||
// public byte[] GetAssociatedText()
|
||||
// {
|
||||
// return associatedText;
|
||||
// }
|
||||
//
|
||||
// public byte[] GetNonce()
|
||||
// {
|
||||
// return nonce;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//public sealed class FastParametersWithIV
|
||||
// : ICipherParameters
|
||||
//{
|
||||
// private readonly ICipherParameters parameters;
|
||||
// private readonly byte[] iv;
|
||||
//
|
||||
// public FastParametersWithIV(ICipherParameters parameters,
|
||||
// byte[] iv)
|
||||
// : this(parameters, iv, 0, iv.Length)
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// public FastParametersWithIV(ICipherParameters parameters,
|
||||
// byte[] iv, int ivOff, int ivLen)
|
||||
// {
|
||||
// // NOTE: 'parameters' may be null to imply key re-use
|
||||
// if (iv == null)
|
||||
// throw new ArgumentNullException("iv");
|
||||
//
|
||||
// this.parameters = parameters;
|
||||
// this.iv = Arrays.CopyOfRange(iv, ivOff, ivOff + ivLen);
|
||||
// }
|
||||
//
|
||||
// public byte[] GetIV()
|
||||
// {
|
||||
// return iv; // (byte[])iv.Clone();
|
||||
// }
|
||||
//
|
||||
// public ICipherParameters Parameters
|
||||
// {
|
||||
// get { return parameters; }
|
||||
// }
|
||||
//}
|
||||
|
||||
internal sealed class FastTlsAeadCipherImpl
|
||||
: TlsAeadCipherImpl
|
||||
{
|
||||
private readonly bool m_isEncrypting;
|
||||
private readonly IAeadCipher m_cipher;
|
||||
|
||||
private KeyParameter key;
|
||||
|
||||
internal FastTlsAeadCipherImpl(IAeadCipher cipher, bool isEncrypting)
|
||||
{
|
||||
this.m_cipher = cipher;
|
||||
this.m_isEncrypting = isEncrypting;
|
||||
}
|
||||
|
||||
public void SetKey(byte[] key, int keyOff, int keyLen)
|
||||
{
|
||||
this.key = new KeyParameter(key, keyOff, keyLen);
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public void SetKey(ReadOnlySpan<byte> key)
|
||||
{
|
||||
this.key = new KeyParameter(key);
|
||||
}
|
||||
#endif
|
||||
|
||||
public void Init(byte[] nonce, int macSize, byte[] additionalData)
|
||||
{
|
||||
m_cipher.Init(m_isEncrypting, new AeadParameters(key, macSize * 8, nonce, additionalData));
|
||||
}
|
||||
|
||||
public int GetOutputSize(int inputLength)
|
||||
{
|
||||
return m_cipher.GetOutputSize(inputLength);
|
||||
}
|
||||
|
||||
public int DoFinal(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
int len = m_cipher.ProcessBytes(input.AsSpan(inputOffset, inputLength), Spans.FromNullable(output, outputOffset));
|
||||
#else
|
||||
int len = m_cipher.ProcessBytes(input, inputOffset, inputLength, output, outputOffset);
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
len += m_cipher.DoFinal(output, outputOffset + len);
|
||||
}
|
||||
catch (InvalidCipherTextException e)
|
||||
{
|
||||
throw new TlsFatalAlert(AlertDescription.bad_record_mac, e);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
m_cipher.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,535 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto.Impl;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
/// <summary>A generic TLS 1.0-1.2 block cipher. This can be used for AES or 3DES for example.</summary>
|
||||
public class FastTlsBlockCipher
|
||||
: TlsCipher
|
||||
{
|
||||
protected readonly TlsCryptoParameters m_cryptoParams;
|
||||
protected readonly byte[] m_randomData;
|
||||
protected readonly bool m_encryptThenMac;
|
||||
protected readonly bool m_useExplicitIV;
|
||||
protected readonly bool m_acceptExtraPadding;
|
||||
protected readonly bool m_useExtraPadding;
|
||||
|
||||
protected readonly TlsBlockCipherImpl m_decryptCipher, m_encryptCipher;
|
||||
protected readonly TlsSuiteMac m_readMac, m_writeMac;
|
||||
|
||||
/// <exception cref="IOException"/>
|
||||
public FastTlsBlockCipher(TlsCryptoParameters cryptoParams, TlsBlockCipherImpl encryptCipher,
|
||||
TlsBlockCipherImpl decryptCipher, TlsHmac clientMac, TlsHmac serverMac, int cipherKeySize)
|
||||
{
|
||||
SecurityParameters securityParameters = cryptoParams.SecurityParameters;
|
||||
ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
|
||||
|
||||
if (TlsImplUtilities.IsTlsV13(negotiatedVersion))
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
|
||||
this.m_cryptoParams = cryptoParams;
|
||||
this.m_randomData = cryptoParams.NonceGenerator.GenerateNonce(256);
|
||||
|
||||
this.m_encryptThenMac = securityParameters.IsEncryptThenMac;
|
||||
this.m_useExplicitIV = TlsImplUtilities.IsTlsV11(negotiatedVersion);
|
||||
|
||||
this.m_acceptExtraPadding = !negotiatedVersion.IsSsl;
|
||||
|
||||
/*
|
||||
* Don't use variable-length padding with truncated MACs.
|
||||
*
|
||||
* See "Tag Size Does Matter: Attacks and Proofs for the TLS Record Protocol", Paterson,
|
||||
* Ristenpart, Shrimpton.
|
||||
*
|
||||
* TODO[DTLS] Consider supporting in DTLS (without exceeding send limit though)
|
||||
*/
|
||||
this.m_useExtraPadding = securityParameters.IsExtendedPadding
|
||||
&& ProtocolVersion.TLSv10.IsEqualOrEarlierVersionOf(negotiatedVersion)
|
||||
&& (m_encryptThenMac || !securityParameters.IsTruncatedHmac);
|
||||
|
||||
this.m_encryptCipher = encryptCipher;
|
||||
this.m_decryptCipher = decryptCipher;
|
||||
|
||||
TlsBlockCipherImpl clientCipher, serverCipher;
|
||||
if (cryptoParams.IsServer)
|
||||
{
|
||||
clientCipher = decryptCipher;
|
||||
serverCipher = encryptCipher;
|
||||
}
|
||||
else
|
||||
{
|
||||
clientCipher = encryptCipher;
|
||||
serverCipher = decryptCipher;
|
||||
}
|
||||
|
||||
int keyBlockSize = (2 * cipherKeySize) + clientMac.MacLength + serverMac.MacLength;
|
||||
|
||||
// From TLS 1.1 onwards, block ciphers don't need IVs from the key_block
|
||||
if (!m_useExplicitIV)
|
||||
{
|
||||
keyBlockSize += clientCipher.GetBlockSize() + serverCipher.GetBlockSize();
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
Span<byte> keyBlock = keyBlockSize <= 512
|
||||
? stackalloc byte[keyBlockSize]
|
||||
: new byte[keyBlockSize];
|
||||
TlsImplUtilities.CalculateKeyBlock(cryptoParams, keyBlock);
|
||||
|
||||
clientMac.SetKey(keyBlock[..clientMac.MacLength]); keyBlock = keyBlock[clientMac.MacLength..];
|
||||
serverMac.SetKey(keyBlock[..serverMac.MacLength]); keyBlock = keyBlock[serverMac.MacLength..];
|
||||
|
||||
clientCipher.SetKey(keyBlock[..cipherKeySize]); keyBlock = keyBlock[cipherKeySize..];
|
||||
serverCipher.SetKey(keyBlock[..cipherKeySize]); keyBlock = keyBlock[cipherKeySize..];
|
||||
|
||||
int clientIVLength = clientCipher.GetBlockSize();
|
||||
int serverIVLength = serverCipher.GetBlockSize();
|
||||
|
||||
if (m_useExplicitIV)
|
||||
{
|
||||
clientCipher.Init(clientIVLength <= 64 ? stackalloc byte[clientIVLength] : new byte[clientIVLength]);
|
||||
serverCipher.Init(serverIVLength <= 64 ? stackalloc byte[serverIVLength] : new byte[serverIVLength]);
|
||||
}
|
||||
else
|
||||
{
|
||||
clientCipher.Init(keyBlock[..clientIVLength]); keyBlock = keyBlock[clientIVLength..];
|
||||
serverCipher.Init(keyBlock[..serverIVLength]); keyBlock = keyBlock[serverIVLength..];
|
||||
}
|
||||
|
||||
if (!keyBlock.IsEmpty)
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
#else
|
||||
byte[] keyBlock = TlsImplUtilities.CalculateKeyBlock(cryptoParams, keyBlockSize);
|
||||
int pos = 0;
|
||||
|
||||
clientMac.SetKey(keyBlock, pos, clientMac.MacLength);
|
||||
pos += clientMac.MacLength;
|
||||
serverMac.SetKey(keyBlock, pos, serverMac.MacLength);
|
||||
pos += serverMac.MacLength;
|
||||
|
||||
clientCipher.SetKey(keyBlock, pos, cipherKeySize);
|
||||
pos += cipherKeySize;
|
||||
serverCipher.SetKey(keyBlock, pos, cipherKeySize);
|
||||
pos += cipherKeySize;
|
||||
|
||||
int clientIVLength = clientCipher.GetBlockSize();
|
||||
int serverIVLength = serverCipher.GetBlockSize();
|
||||
|
||||
if (m_useExplicitIV)
|
||||
{
|
||||
clientCipher.Init(new byte[clientIVLength], 0, clientIVLength);
|
||||
serverCipher.Init(new byte[serverIVLength], 0, serverIVLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
clientCipher.Init(keyBlock, pos, clientIVLength);
|
||||
pos += clientIVLength;
|
||||
serverCipher.Init(keyBlock, pos, serverIVLength);
|
||||
pos += serverIVLength;
|
||||
}
|
||||
|
||||
if (pos != keyBlockSize)
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
#endif
|
||||
|
||||
if (cryptoParams.IsServer)
|
||||
{
|
||||
this.m_writeMac = new TlsSuiteHmac(cryptoParams, serverMac);
|
||||
this.m_readMac = new TlsSuiteHmac(cryptoParams, clientMac);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.m_writeMac = new TlsSuiteHmac(cryptoParams, clientMac);
|
||||
this.m_readMac = new TlsSuiteHmac(cryptoParams, serverMac);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int GetCiphertextDecodeLimit(int plaintextLimit)
|
||||
{
|
||||
int blockSize = m_decryptCipher.GetBlockSize();
|
||||
int macSize = m_readMac.Size;
|
||||
int maxPadding = 256;
|
||||
|
||||
return GetCiphertextLength(blockSize, macSize, maxPadding, plaintextLimit);
|
||||
}
|
||||
|
||||
public virtual int GetCiphertextEncodeLimit(int plaintextLength, int plaintextLimit)
|
||||
{
|
||||
int blockSize = m_encryptCipher.GetBlockSize();
|
||||
int macSize = m_writeMac.Size;
|
||||
int maxPadding = m_useExtraPadding ? 256 : blockSize;
|
||||
|
||||
return GetCiphertextLength(blockSize, macSize, maxPadding, plaintextLength);
|
||||
}
|
||||
|
||||
public virtual int GetPlaintextLimit(int ciphertextLimit)
|
||||
{
|
||||
int blockSize = m_encryptCipher.GetBlockSize();
|
||||
int macSize = m_writeMac.Size;
|
||||
|
||||
int plaintextLimit = ciphertextLimit;
|
||||
|
||||
// Leave room for the MAC, and require block-alignment
|
||||
if (m_encryptThenMac)
|
||||
{
|
||||
plaintextLimit -= macSize;
|
||||
plaintextLimit -= plaintextLimit % blockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
plaintextLimit -= plaintextLimit % blockSize;
|
||||
plaintextLimit -= macSize;
|
||||
}
|
||||
|
||||
// Minimum 1 byte of padding
|
||||
--plaintextLimit;
|
||||
|
||||
// An explicit IV consumes 1 block
|
||||
if (m_useExplicitIV)
|
||||
{
|
||||
plaintextLimit -= blockSize;
|
||||
}
|
||||
|
||||
return plaintextLimit;
|
||||
}
|
||||
|
||||
public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
|
||||
int headerAllocation, byte[] plaintext, int offset, int len)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
return EncodePlaintext(seqNo, contentType, recordVersion, headerAllocation, plaintext.AsSpan(offset, len));
|
||||
#else
|
||||
int blockSize = m_encryptCipher.GetBlockSize();
|
||||
int macSize = m_writeMac.Size;
|
||||
|
||||
int enc_input_length = len;
|
||||
if (!m_encryptThenMac)
|
||||
{
|
||||
enc_input_length += macSize;
|
||||
}
|
||||
|
||||
int padding_length = blockSize - (enc_input_length % blockSize);
|
||||
if (m_useExtraPadding)
|
||||
{
|
||||
// Add a random number of extra blocks worth of padding
|
||||
int maxExtraPadBlocks = (256 - padding_length) / blockSize;
|
||||
int actualExtraPadBlocks = ChooseExtraPadBlocks(maxExtraPadBlocks);
|
||||
padding_length += actualExtraPadBlocks * blockSize;
|
||||
}
|
||||
|
||||
int totalSize = len + macSize + padding_length;
|
||||
if (m_useExplicitIV)
|
||||
{
|
||||
totalSize += blockSize;
|
||||
}
|
||||
|
||||
byte[] outBuf = new byte[headerAllocation + totalSize];
|
||||
int outOff = headerAllocation;
|
||||
|
||||
if (m_useExplicitIV)
|
||||
{
|
||||
// Technically the explicit IV will be the encryption of this nonce
|
||||
byte[] explicitIV = m_cryptoParams.NonceGenerator.GenerateNonce(blockSize);
|
||||
Array.Copy(explicitIV, 0, outBuf, outOff, blockSize);
|
||||
outOff += blockSize;
|
||||
}
|
||||
|
||||
Array.Copy(plaintext, offset, outBuf, outOff, len);
|
||||
outOff += len;
|
||||
|
||||
if (!m_encryptThenMac)
|
||||
{
|
||||
byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, plaintext, offset, len);
|
||||
Array.Copy(mac, 0, outBuf, outOff, mac.Length);
|
||||
outOff += mac.Length;
|
||||
}
|
||||
|
||||
byte padByte = (byte)(padding_length - 1);
|
||||
for (int i = 0; i < padding_length; ++i)
|
||||
{
|
||||
outBuf[outOff++] = padByte;
|
||||
}
|
||||
|
||||
m_encryptCipher.DoFinal(outBuf, headerAllocation, outOff - headerAllocation, outBuf, headerAllocation);
|
||||
|
||||
if (m_encryptThenMac)
|
||||
{
|
||||
byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, outBuf, headerAllocation,
|
||||
outOff - headerAllocation);
|
||||
Array.Copy(mac, 0, outBuf, outOff, mac.Length);
|
||||
outOff += mac.Length;
|
||||
}
|
||||
|
||||
if (outOff != outBuf.Length)
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
|
||||
return new TlsEncodeResult(outBuf, 0, outBuf.Length, contentType);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
|
||||
int headerAllocation, ReadOnlySpan<byte> plaintext)
|
||||
{
|
||||
int blockSize = m_encryptCipher.GetBlockSize();
|
||||
int macSize = m_writeMac.Size;
|
||||
|
||||
int enc_input_length = plaintext.Length;
|
||||
if (!m_encryptThenMac)
|
||||
{
|
||||
enc_input_length += macSize;
|
||||
}
|
||||
|
||||
int padding_length = blockSize - (enc_input_length % blockSize);
|
||||
if (m_useExtraPadding)
|
||||
{
|
||||
// Add a random number of extra blocks worth of padding
|
||||
int maxExtraPadBlocks = (256 - padding_length) / blockSize;
|
||||
int actualExtraPadBlocks = ChooseExtraPadBlocks(maxExtraPadBlocks);
|
||||
padding_length += actualExtraPadBlocks * blockSize;
|
||||
}
|
||||
|
||||
int totalSize = plaintext.Length + macSize + padding_length;
|
||||
if (m_useExplicitIV)
|
||||
{
|
||||
totalSize += blockSize;
|
||||
}
|
||||
|
||||
byte[] outBuf = new byte[headerAllocation + totalSize];
|
||||
int outOff = headerAllocation;
|
||||
|
||||
if (m_useExplicitIV)
|
||||
{
|
||||
// Technically the explicit IV will be the encryption of this nonce
|
||||
byte[] explicitIV = m_cryptoParams.NonceGenerator.GenerateNonce(blockSize);
|
||||
Array.Copy(explicitIV, 0, outBuf, outOff, blockSize);
|
||||
outOff += blockSize;
|
||||
}
|
||||
|
||||
plaintext.CopyTo(outBuf.AsSpan(outOff));
|
||||
outOff += plaintext.Length;
|
||||
|
||||
if (!m_encryptThenMac)
|
||||
{
|
||||
byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, plaintext);
|
||||
mac.CopyTo(outBuf.AsSpan(outOff));
|
||||
outOff += mac.Length;
|
||||
}
|
||||
|
||||
byte padByte = (byte)(padding_length - 1);
|
||||
for (int i = 0; i < padding_length; ++i)
|
||||
{
|
||||
outBuf[outOff++] = padByte;
|
||||
}
|
||||
|
||||
m_encryptCipher.DoFinal(outBuf, headerAllocation, outOff - headerAllocation, outBuf, headerAllocation);
|
||||
|
||||
if (m_encryptThenMac)
|
||||
{
|
||||
byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, outBuf, headerAllocation,
|
||||
outOff - headerAllocation);
|
||||
Array.Copy(mac, 0, outBuf, outOff, mac.Length);
|
||||
outOff += mac.Length;
|
||||
}
|
||||
|
||||
if (outOff != outBuf.Length)
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
|
||||
return new TlsEncodeResult(outBuf, 0, outBuf.Length, contentType);
|
||||
}
|
||||
#endif
|
||||
|
||||
public virtual TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
|
||||
byte[] ciphertext, int offset, int len)
|
||||
{
|
||||
int blockSize = m_decryptCipher.GetBlockSize();
|
||||
int macSize = m_readMac.Size;
|
||||
|
||||
int minLen = blockSize;
|
||||
if (m_encryptThenMac)
|
||||
{
|
||||
minLen += macSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
minLen = System.Math.Max(minLen, macSize + 1);
|
||||
}
|
||||
|
||||
if (m_useExplicitIV)
|
||||
{
|
||||
minLen += blockSize;
|
||||
}
|
||||
|
||||
if (len < minLen)
|
||||
throw new TlsFatalAlert(AlertDescription.decode_error);
|
||||
|
||||
int blocks_length = len;
|
||||
if (m_encryptThenMac)
|
||||
{
|
||||
blocks_length -= macSize;
|
||||
}
|
||||
|
||||
if (blocks_length % blockSize != 0)
|
||||
throw new TlsFatalAlert(AlertDescription.decryption_failed);
|
||||
|
||||
if (m_encryptThenMac)
|
||||
{
|
||||
byte[] expectedMac = m_readMac.CalculateMac(seqNo, recordType, ciphertext, offset, len - macSize);
|
||||
|
||||
bool checkMac = TlsUtilities.ConstantTimeAreEqual(macSize, expectedMac, 0, ciphertext,
|
||||
offset + len - macSize);
|
||||
if (!checkMac)
|
||||
{
|
||||
/*
|
||||
* RFC 7366 3. The MAC SHALL be evaluated before any further processing such as
|
||||
* decryption is performed, and if the MAC verification fails, then processing SHALL
|
||||
* terminate immediately. For TLS, a fatal bad_record_mac MUST be generated [2]. For
|
||||
* DTLS, the record MUST be discarded, and a fatal bad_record_mac MAY be generated
|
||||
* [4]. This immediate response to a bad MAC eliminates any timing channels that may
|
||||
* be available through the use of manipulated packet data.
|
||||
*/
|
||||
throw new TlsFatalAlert(AlertDescription.bad_record_mac);
|
||||
}
|
||||
}
|
||||
|
||||
m_decryptCipher.DoFinal(ciphertext, offset, blocks_length, ciphertext, offset);
|
||||
|
||||
if (m_useExplicitIV)
|
||||
{
|
||||
offset += blockSize;
|
||||
blocks_length -= blockSize;
|
||||
}
|
||||
|
||||
// If there's anything wrong with the padding, this will return zero
|
||||
int totalPad = CheckPaddingConstantTime(ciphertext, offset, blocks_length, blockSize,
|
||||
m_encryptThenMac ? 0 : macSize);
|
||||
bool badMac = (totalPad == 0);
|
||||
|
||||
int dec_output_length = blocks_length - totalPad;
|
||||
|
||||
if (!m_encryptThenMac)
|
||||
{
|
||||
dec_output_length -= macSize;
|
||||
|
||||
byte[] expectedMac = m_readMac.CalculateMacConstantTime(seqNo, recordType, ciphertext, offset,
|
||||
dec_output_length, blocks_length - macSize, m_randomData);
|
||||
|
||||
badMac |= !TlsUtilities.ConstantTimeAreEqual(macSize, expectedMac, 0, ciphertext,
|
||||
offset + dec_output_length);
|
||||
}
|
||||
|
||||
if (badMac)
|
||||
throw new TlsFatalAlert(AlertDescription.bad_record_mac);
|
||||
|
||||
return new TlsDecodeResult(ciphertext, offset, dec_output_length, recordType);
|
||||
}
|
||||
|
||||
public virtual void RekeyDecoder()
|
||||
{
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
}
|
||||
|
||||
public virtual void RekeyEncoder()
|
||||
{
|
||||
throw new TlsFatalAlert(AlertDescription.internal_error);
|
||||
}
|
||||
|
||||
public virtual bool UsesOpaqueRecordType
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
protected virtual int CheckPaddingConstantTime(byte[] buf, int off, int len, int blockSize, int macSize)
|
||||
{
|
||||
int end = off + len;
|
||||
byte lastByte = buf[end - 1];
|
||||
int padlen = lastByte & 0xff;
|
||||
int totalPad = padlen + 1;
|
||||
|
||||
int dummyIndex = 0;
|
||||
byte padDiff = 0;
|
||||
|
||||
int totalPadLimit = System.Math.Min(m_acceptExtraPadding ? 256 : blockSize, len - macSize);
|
||||
|
||||
if (totalPad > totalPadLimit)
|
||||
{
|
||||
totalPad = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
int padPos = end - totalPad;
|
||||
do
|
||||
{
|
||||
padDiff |= (byte)(buf[padPos++] ^ lastByte);
|
||||
}
|
||||
while (padPos < end);
|
||||
|
||||
dummyIndex = totalPad;
|
||||
|
||||
if (padDiff != 0)
|
||||
{
|
||||
totalPad = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Run some extra dummy checks so the number of checks is always constant
|
||||
{
|
||||
byte[] dummyPad = m_randomData;
|
||||
while (dummyIndex < 256)
|
||||
{
|
||||
padDiff |= (byte)(dummyPad[dummyIndex++] ^ lastByte);
|
||||
}
|
||||
// Ensure the above loop is not eliminated
|
||||
dummyPad[0] ^= padDiff;
|
||||
}
|
||||
|
||||
return totalPad;
|
||||
}
|
||||
|
||||
protected virtual int ChooseExtraPadBlocks(int max)
|
||||
{
|
||||
byte[] random = m_cryptoParams.NonceGenerator.GenerateNonce(4);
|
||||
int x = (int)Pack.LE_To_UInt32(random, 0);
|
||||
int n = Integers.NumberOfTrailingZeros(x);
|
||||
return System.Math.Min(n, max);
|
||||
}
|
||||
|
||||
protected virtual int GetCiphertextLength(int blockSize, int macSize, int maxPadding, int plaintextLength)
|
||||
{
|
||||
int ciphertextLength = plaintextLength;
|
||||
|
||||
// An explicit IV consumes 1 block
|
||||
if (m_useExplicitIV)
|
||||
{
|
||||
ciphertextLength += blockSize;
|
||||
}
|
||||
|
||||
// Leave room for the MAC and (block-aligning) padding
|
||||
|
||||
ciphertextLength += maxPadding;
|
||||
|
||||
if (m_encryptThenMac)
|
||||
{
|
||||
ciphertextLength -= (ciphertextLength % blockSize);
|
||||
ciphertextLength += macSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
ciphertextLength += macSize;
|
||||
ciphertextLength -= (ciphertextLength % blockSize);
|
||||
}
|
||||
|
||||
return ciphertextLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
#pragma warning disable
|
||||
using System;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto.Impl;
|
||||
|
||||
namespace BestHTTP.Connections.TLS.Crypto.Impl
|
||||
{
|
||||
internal sealed class FastTlsBlockCipherImpl
|
||||
: TlsBlockCipherImpl
|
||||
{
|
||||
private readonly bool m_isEncrypting;
|
||||
private readonly IBlockCipher m_cipher;
|
||||
|
||||
private KeyParameter key;
|
||||
|
||||
internal FastTlsBlockCipherImpl(IBlockCipher cipher, bool isEncrypting)
|
||||
{
|
||||
this.m_cipher = cipher;
|
||||
this.m_isEncrypting = isEncrypting;
|
||||
}
|
||||
|
||||
public void SetKey(byte[] key, int keyOff, int keyLen)
|
||||
{
|
||||
this.key = new KeyParameter(key, keyOff, keyLen);
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public void SetKey(ReadOnlySpan<byte> key)
|
||||
{
|
||||
this.key = new KeyParameter(key);
|
||||
}
|
||||
#endif
|
||||
|
||||
public void Init(byte[] iv, int ivOff, int ivLen)
|
||||
{
|
||||
m_cipher.Init(m_isEncrypting, new ParametersWithIV(key, iv, ivOff, ivLen));
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
|
||||
public void Init(ReadOnlySpan<byte> iv)
|
||||
{
|
||||
m_cipher.Init(m_isEncrypting, new ParametersWithIV(key, iv));
|
||||
}
|
||||
#endif
|
||||
|
||||
public int DoFinal(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset)
|
||||
{
|
||||
int blockSize = m_cipher.GetBlockSize();
|
||||
|
||||
for (int i = 0; i < inputLength; i += blockSize)
|
||||
{
|
||||
m_cipher.ProcessBlock(input, inputOffset + i, output, outputOffset + i);
|
||||
}
|
||||
|
||||
return inputLength;
|
||||
}
|
||||
|
||||
public int GetBlockSize()
|
||||
{
|
||||
return m_cipher.GetBlockSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BestHTTP.Connections.TLS.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Security;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls;
|
||||
|
||||
namespace BestHTTP.Connections.TLS
|
||||
{
|
||||
public class DefaultTls13Client : AbstractTls13Client
|
||||
{
|
||||
public DefaultTls13Client(HTTPRequest request, List<ServerName> sniServerNames, List<ProtocolName> protocols)
|
||||
: base(request, sniServerNames, protocols, new FastTlsCrypto(new SecureRandom()))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto.Impl;
|
||||
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Encoders;
|
||||
|
||||
// https://www.m00nie.com/2015/05/decrypt-https-ssltls-with-wireshark/
|
||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
|
||||
// https://github.com/bcgit/bc-csharp/issues/343
|
||||
|
||||
namespace BestHTTP.Connections.TLS
|
||||
{
|
||||
/// <summary>
|
||||
/// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
|
||||
/// </summary>
|
||||
internal enum Labels
|
||||
{
|
||||
CLIENT_RANDOM,
|
||||
CLIENT_EARLY_TRAFFIC_SECRET,
|
||||
CLIENT_HANDSHAKE_TRAFFIC_SECRET,
|
||||
SERVER_HANDSHAKE_TRAFFIC_SECRET,
|
||||
CLIENT_TRAFFIC_SECRET_0,
|
||||
SERVER_TRAFFIC_SECRET_0,
|
||||
EARLY_EXPORTER_SECRET,
|
||||
EXPORTER_SECRET
|
||||
}
|
||||
|
||||
internal static class KeyLogFileWriter
|
||||
{
|
||||
private static string GetKeylogFileName() => Environment.GetEnvironmentVariable("SSLKEYLOGFILE", EnvironmentVariableTarget.User);
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void WriteLabel(Labels label, byte[] clientRandom, TlsSecret secret)
|
||||
{
|
||||
if (clientRandom != null && secret != null)
|
||||
{
|
||||
string SSLKEYLOGFILE = GetKeylogFileName();
|
||||
if (!string.IsNullOrEmpty(SSLKEYLOGFILE))
|
||||
using (var writer = new StreamWriter(System.IO.File.Open(SSLKEYLOGFILE, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)))
|
||||
writer.WriteLine($"{label} {Hex.ToHexString(clientRandom)} {Hex.ToHexString((secret as AbstractTlsSecret).CopyData())}");
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void WriteLabel(Labels label, SecurityParameters securityParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
TlsSecret secret = null;
|
||||
switch (label)
|
||||
{
|
||||
case Labels.CLIENT_RANDOM: secret = securityParameters.MasterSecret; break;
|
||||
case Labels.CLIENT_HANDSHAKE_TRAFFIC_SECRET: secret = securityParameters.TrafficSecretClient; break;
|
||||
case Labels.SERVER_HANDSHAKE_TRAFFIC_SECRET: secret = securityParameters.TrafficSecretServer; break;
|
||||
case Labels.CLIENT_TRAFFIC_SECRET_0: secret = securityParameters.TrafficSecretClient; break;
|
||||
case Labels.SERVER_TRAFFIC_SECRET_0: secret = securityParameters.TrafficSecretServer; break;
|
||||
case Labels.EXPORTER_SECRET: secret = securityParameters.ExporterMasterSecret; break;
|
||||
|
||||
case Labels.CLIENT_EARLY_TRAFFIC_SECRET: break;
|
||||
case Labels.EARLY_EXPORTER_SECRET: break;
|
||||
}
|
||||
|
||||
if (secret != null)
|
||||
WriteLabel(label, securityParameters.ClientRandom, secret);
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,434 @@
|
|||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
using BestHTTP.Caching;
|
||||
#endif
|
||||
using BestHTTP.Core;
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.Connections;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
|
||||
namespace BestHTTP.Connections
|
||||
{
|
||||
delegate void OnWebGLRequestHandlerDelegate(int nativeId, int httpStatus, IntPtr pBuffer, int length, int zero);
|
||||
delegate void OnWebGLBufferDelegate(int nativeId, IntPtr pBuffer, int length);
|
||||
delegate void OnWebGLProgressDelegate(int nativeId, int downloaded, int total);
|
||||
delegate void OnWebGLErrorDelegate(int nativeId, string error);
|
||||
delegate void OnWebGLTimeoutDelegate(int nativeId);
|
||||
delegate void OnWebGLAbortedDelegate(int nativeId);
|
||||
|
||||
internal sealed class WebGLConnection : ConnectionBase
|
||||
{
|
||||
static Dictionary<int, WebGLConnection> Connections = new Dictionary<int, WebGLConnection>(4);
|
||||
|
||||
int NativeId;
|
||||
BufferSegmentStream Stream;
|
||||
|
||||
public WebGLConnection(string serverAddress)
|
||||
: base(serverAddress, false)
|
||||
{
|
||||
XHR_SetLoglevel((byte)HTTPManager.Logger.Level);
|
||||
}
|
||||
|
||||
public override void Shutdown(ShutdownTypes type)
|
||||
{
|
||||
base.Shutdown(type);
|
||||
|
||||
XHR_Abort(this.NativeId);
|
||||
}
|
||||
|
||||
protected override void ThreadFunc()
|
||||
{
|
||||
// XmlHttpRequest setup
|
||||
|
||||
this.NativeId = XHR_Create(HTTPRequest.MethodNames[(byte)CurrentRequest.MethodType],
|
||||
CurrentRequest.CurrentUri.OriginalString,
|
||||
CurrentRequest.Credentials != null ? CurrentRequest.Credentials.UserName : null,
|
||||
CurrentRequest.Credentials != null ? CurrentRequest.Credentials.Password : null,
|
||||
CurrentRequest.WithCredentials ? 1 : 0);
|
||||
Connections.Add(NativeId, this);
|
||||
|
||||
CurrentRequest.EnumerateHeaders((header, values) =>
|
||||
{
|
||||
if (!header.Equals("Content-Length"))
|
||||
for (int i = 0; i < values.Count; ++i)
|
||||
XHR_SetRequestHeader(NativeId, header, values[i]);
|
||||
}, /*callBeforeSendCallback:*/ true);
|
||||
|
||||
XHR_SetResponseHandler(NativeId, WebGLConnection.OnResponse, WebGLConnection.OnError, WebGLConnection.OnTimeout, WebGLConnection.OnAborted);
|
||||
// Setting OnUploadProgress result in an addEventListener("progress", ...) call making the request non-simple.
|
||||
// https://forum.unity.com/threads/best-http-released.200006/page-49#post-3696220
|
||||
XHR_SetProgressHandler(NativeId,
|
||||
CurrentRequest.OnDownloadProgress == null ? (OnWebGLProgressDelegate)null : WebGLConnection.OnDownloadProgress,
|
||||
CurrentRequest.OnUploadProgress == null ? (OnWebGLProgressDelegate)null : WebGLConnection.OnUploadProgress);
|
||||
|
||||
XHR_SetTimeout(NativeId, (uint)(CurrentRequest.ConnectTimeout.TotalMilliseconds + CurrentRequest.Timeout.TotalMilliseconds));
|
||||
|
||||
byte[] body = CurrentRequest.GetEntityBody();
|
||||
int length = 0;
|
||||
bool releaseBodyBuffer = false;
|
||||
|
||||
if (body == null)
|
||||
{
|
||||
var upStreamInfo = CurrentRequest.GetUpStream();
|
||||
if (upStreamInfo.Stream != null)
|
||||
{
|
||||
var internalBuffer = BufferPool.Get(upStreamInfo.Length > 0 ? upStreamInfo.Length : HTTPRequest.UploadChunkSize, true);
|
||||
using (BufferPoolMemoryStream ms = new BufferPoolMemoryStream(internalBuffer, 0, internalBuffer.Length, true, true, false, true))
|
||||
{
|
||||
var buffer = BufferPool.Get(HTTPRequest.UploadChunkSize, true);
|
||||
int readCount = -1;
|
||||
while ((readCount = upStreamInfo.Stream.Read(buffer, 0, buffer.Length)) > 0)
|
||||
ms.Write(buffer, 0, readCount);
|
||||
|
||||
BufferPool.Release(buffer);
|
||||
|
||||
length = (int)ms.Position;
|
||||
body = ms.GetBuffer();
|
||||
|
||||
releaseBodyBuffer = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
length = body.Length;
|
||||
}
|
||||
|
||||
XHR_Send(NativeId, body, length);
|
||||
|
||||
if (releaseBodyBuffer)
|
||||
BufferPool.Release(body);
|
||||
}
|
||||
|
||||
#region Callback Implementations
|
||||
|
||||
void OnResponse(int httpStatus, BufferSegment payload)
|
||||
{
|
||||
HTTPConnectionStates proposedConnectionState = HTTPConnectionStates.Processing;
|
||||
bool resendRequest = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (this.CurrentRequest.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
using (var ms = new BufferSegmentStream())
|
||||
{
|
||||
Stream = ms;
|
||||
|
||||
XHR_GetStatusLine(NativeId, OnBufferCallback);
|
||||
XHR_GetResponseHeaders(NativeId, OnBufferCallback);
|
||||
|
||||
if (payload != BufferSegment.Empty)
|
||||
ms.Write(payload);
|
||||
|
||||
SupportedProtocols protocol = HTTPProtocolFactory.GetProtocolFromUri(CurrentRequest.CurrentUri);
|
||||
CurrentRequest.Response = HTTPProtocolFactory.Get(protocol, CurrentRequest, ms, CurrentRequest.UseStreaming, false);
|
||||
|
||||
CurrentRequest.Response.Receive(payload != BufferSegment.Empty && payload.Count > 0 ? (int)payload.Count : -1, true);
|
||||
|
||||
KeepAliveHeader keepAlive = null;
|
||||
ConnectionHelper.HandleResponse(this.ToString(), this.CurrentRequest, out resendRequest, out proposedConnectionState, ref keepAlive);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
HTTPManager.Logger.Exception(this.NativeId + " WebGLConnection", "OnResponse", e, this.Context);
|
||||
|
||||
if (this.ShutdownType == ShutdownTypes.Immediate)
|
||||
return;
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
if (this.CurrentRequest.UseStreaming)
|
||||
HTTPCacheService.DeleteEntity(this.CurrentRequest.CurrentUri);
|
||||
#endif
|
||||
|
||||
// Something gone bad, Response must be null!
|
||||
this.CurrentRequest.Response = null;
|
||||
|
||||
if (!this.CurrentRequest.IsCancellationRequested)
|
||||
{
|
||||
this.CurrentRequest.Exception = e;
|
||||
this.CurrentRequest.State = HTTPRequestStates.Error;
|
||||
}
|
||||
|
||||
proposedConnectionState = HTTPConnectionStates.Closed;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Exit ASAP
|
||||
if (this.ShutdownType != ShutdownTypes.Immediate)
|
||||
{
|
||||
if (this.CurrentRequest.IsCancellationRequested)
|
||||
{
|
||||
// we don't know what stage the request is cancelled, we can't safely reuse the tcp channel.
|
||||
proposedConnectionState = HTTPConnectionStates.Closed;
|
||||
|
||||
this.CurrentRequest.Response = null;
|
||||
|
||||
this.CurrentRequest.State = this.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
|
||||
}
|
||||
else if (resendRequest)
|
||||
{
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.Resend));
|
||||
}
|
||||
else if (this.CurrentRequest.Response != null && this.CurrentRequest.Response.IsUpgraded)
|
||||
{
|
||||
proposedConnectionState = HTTPConnectionStates.WaitForProtocolShutdown;
|
||||
}
|
||||
else if (this.CurrentRequest.State == HTTPRequestStates.Processing)
|
||||
{
|
||||
if (this.CurrentRequest.Response != null)
|
||||
this.CurrentRequest.State = HTTPRequestStates.Finished;
|
||||
else
|
||||
{
|
||||
this.CurrentRequest.Exception = new Exception(string.Format("[{0}] Remote server closed the connection before sending response header! Previous request state: {1}. Connection state: {2}",
|
||||
this.ToString(),
|
||||
this.CurrentRequest.State.ToString(),
|
||||
this.State.ToString()));
|
||||
this.CurrentRequest.State = HTTPRequestStates.Error;
|
||||
|
||||
proposedConnectionState = HTTPConnectionStates.Closed;
|
||||
}
|
||||
}
|
||||
|
||||
this.CurrentRequest = null;
|
||||
|
||||
if (proposedConnectionState == HTTPConnectionStates.Processing)
|
||||
proposedConnectionState = HTTPConnectionStates.Closed;
|
||||
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, proposedConnectionState));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnBuffer(BufferSegment buffer)
|
||||
{
|
||||
if (Stream != null)
|
||||
{
|
||||
Stream.Write(buffer);
|
||||
//Stream.Write(HTTPRequest.EOL, 0, HTTPRequest.EOL.Length);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDownloadProgress(int down, int total)
|
||||
{
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.DownloadProgress, down, total));
|
||||
}
|
||||
|
||||
void OnUploadProgress(int up, int total)
|
||||
{
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.UploadProgress, up, total));
|
||||
}
|
||||
|
||||
void OnError(string error)
|
||||
{
|
||||
HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnError", error, this.Context);
|
||||
|
||||
LastProcessTime = DateTime.UtcNow;
|
||||
|
||||
CurrentRequest.Response = null;
|
||||
CurrentRequest.Exception = new Exception(error);
|
||||
CurrentRequest.State = HTTPRequestStates.Error;
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
|
||||
}
|
||||
|
||||
void OnTimeout()
|
||||
{
|
||||
HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnResponse", string.Empty, this.Context);
|
||||
|
||||
CurrentRequest.Response = null;
|
||||
CurrentRequest.State = HTTPRequestStates.TimedOut;
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
|
||||
}
|
||||
|
||||
void OnAborted()
|
||||
{
|
||||
HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnAborted", string.Empty, this.Context);
|
||||
|
||||
CurrentRequest.Response = null;
|
||||
CurrentRequest.State = HTTPRequestStates.Aborted;
|
||||
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Connections.Remove(NativeId);
|
||||
XHR_Release(NativeId);
|
||||
|
||||
Stream = null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region WebGL Static Callbacks
|
||||
|
||||
[AOT.MonoPInvokeCallback(typeof(OnWebGLRequestHandlerDelegate))]
|
||||
static void OnResponse(int nativeId, int httpStatus, IntPtr pBuffer, int length, int err)
|
||||
{
|
||||
WebGLConnection conn = null;
|
||||
if (!Connections.TryGetValue(nativeId, out conn))
|
||||
{
|
||||
HTTPManager.Logger.Error("WebGLConnection - OnResponse", "No WebGL connection found for nativeId: " + nativeId.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
HTTPManager.Logger.Information("WebGLConnection - OnResponse", string.Format("{0} {1} {2} {3}", nativeId, httpStatus, length, err), conn.Context);
|
||||
|
||||
BufferSegment payload = BufferSegment.Empty;
|
||||
if (length > 0)
|
||||
{
|
||||
var buffer = BufferPool.Get(length, true);
|
||||
|
||||
XHR_CopyResponseTo(nativeId, buffer, length);
|
||||
|
||||
payload = new BufferSegment(buffer, 0, length);
|
||||
}
|
||||
|
||||
conn.OnResponse(httpStatus, payload);
|
||||
}
|
||||
|
||||
[AOT.MonoPInvokeCallback(typeof(OnWebGLBufferDelegate))]
|
||||
static void OnBufferCallback(int nativeId, IntPtr pBuffer, int length)
|
||||
{
|
||||
WebGLConnection conn = null;
|
||||
if (!Connections.TryGetValue(nativeId, out conn))
|
||||
{
|
||||
HTTPManager.Logger.Error("WebGLConnection - OnBufferCallback", "No WebGL connection found for nativeId: " + nativeId.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] buffer = BufferPool.Get(length, true);
|
||||
|
||||
// Copy data from the 'unmanaged' memory to managed memory. Buffer will be reclaimed by the GC.
|
||||
Marshal.Copy(pBuffer, buffer, 0, length);
|
||||
|
||||
conn.OnBuffer(new BufferSegment(buffer, 0, length));
|
||||
}
|
||||
|
||||
[AOT.MonoPInvokeCallback(typeof(OnWebGLProgressDelegate))]
|
||||
static void OnDownloadProgress(int nativeId, int downloaded, int total)
|
||||
{
|
||||
WebGLConnection conn = null;
|
||||
if (!Connections.TryGetValue(nativeId, out conn))
|
||||
{
|
||||
HTTPManager.Logger.Error("WebGLConnection - OnDownloadProgress", "No WebGL connection found for nativeId: " + nativeId.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
HTTPManager.Logger.Information(nativeId + " OnDownloadProgress", downloaded.ToString() + " / " + total.ToString(), conn.Context);
|
||||
|
||||
conn.OnDownloadProgress(downloaded, total);
|
||||
}
|
||||
|
||||
[AOT.MonoPInvokeCallback(typeof(OnWebGLProgressDelegate))]
|
||||
static void OnUploadProgress(int nativeId, int uploaded, int total)
|
||||
{
|
||||
WebGLConnection conn = null;
|
||||
if (!Connections.TryGetValue(nativeId, out conn))
|
||||
{
|
||||
HTTPManager.Logger.Error("WebGLConnection - OnUploadProgress", "No WebGL connection found for nativeId: " + nativeId.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
HTTPManager.Logger.Information(nativeId + " OnUploadProgress", uploaded.ToString() + " / " + total.ToString(), conn.Context);
|
||||
|
||||
conn.OnUploadProgress(uploaded, total);
|
||||
}
|
||||
|
||||
[AOT.MonoPInvokeCallback(typeof(OnWebGLErrorDelegate))]
|
||||
static void OnError(int nativeId, string error)
|
||||
{
|
||||
WebGLConnection conn = null;
|
||||
if (!Connections.TryGetValue(nativeId, out conn))
|
||||
{
|
||||
HTTPManager.Logger.Error("WebGLConnection - OnError", "No WebGL connection found for nativeId: " + nativeId.ToString() + " Error: " + error);
|
||||
return;
|
||||
}
|
||||
|
||||
conn.OnError(error);
|
||||
}
|
||||
|
||||
[AOT.MonoPInvokeCallback(typeof(OnWebGLTimeoutDelegate))]
|
||||
static void OnTimeout(int nativeId)
|
||||
{
|
||||
WebGLConnection conn = null;
|
||||
if (!Connections.TryGetValue(nativeId, out conn))
|
||||
{
|
||||
HTTPManager.Logger.Error("WebGLConnection - OnTimeout", "No WebGL connection found for nativeId: " + nativeId.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
conn.OnTimeout();
|
||||
}
|
||||
|
||||
[AOT.MonoPInvokeCallback(typeof(OnWebGLAbortedDelegate))]
|
||||
static void OnAborted(int nativeId)
|
||||
{
|
||||
WebGLConnection conn = null;
|
||||
if (!Connections.TryGetValue(nativeId, out conn))
|
||||
{
|
||||
HTTPManager.Logger.Error("WebGLConnection - OnAborted", "No WebGL connection found for nativeId: " + nativeId.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
conn.OnAborted();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region WebGL Interface
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int XHR_Create(string method, string url, string userName, string passwd, int withCredentials);
|
||||
|
||||
/// <summary>
|
||||
/// Is an unsigned long representing the number of milliseconds a request can take before automatically being terminated. A value of 0 (which is the default) means there is no timeout.
|
||||
/// </summary>
|
||||
[DllImport("__Internal")]
|
||||
private static extern void XHR_SetTimeout(int nativeId, uint timeout);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void XHR_SetRequestHeader(int nativeId, string header, string value);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void XHR_SetResponseHandler(int nativeId, OnWebGLRequestHandlerDelegate onresponse, OnWebGLErrorDelegate onerror, OnWebGLTimeoutDelegate ontimeout, OnWebGLAbortedDelegate onabort);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void XHR_SetProgressHandler(int nativeId, OnWebGLProgressDelegate onDownloadProgress, OnWebGLProgressDelegate onUploadProgress);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void XHR_CopyResponseTo(int nativeId, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] response, int length);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void XHR_Send(int nativeId, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] body, int length);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void XHR_GetResponseHeaders(int nativeId, OnWebGLBufferDelegate callback);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void XHR_GetStatusLine(int nativeId, OnWebGLBufferDelegate callback);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void XHR_Abort(int nativeId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void XHR_Release(int nativeId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void XHR_SetLoglevel(int logLevel);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,377 @@
|
|||
#if !BESTHTTP_DISABLE_COOKIES
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BestHTTP.Extensions;
|
||||
using System.IO;
|
||||
|
||||
namespace BestHTTP.Cookies
|
||||
{
|
||||
/// <summary>
|
||||
/// The Cookie implementation based on RFC 6265(http://tools.ietf.org/html/rfc6265).
|
||||
/// </summary>
|
||||
public sealed class Cookie : IComparable<Cookie>, IEquatable<Cookie>
|
||||
{
|
||||
private const int Version = 1;
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The name of the cookie.
|
||||
/// </summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value of the cookie.
|
||||
/// </summary>
|
||||
public string Value { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Date when the Cookie is registered.
|
||||
/// </summary>
|
||||
public DateTime Date { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// When this Cookie last used in a request.
|
||||
/// </summary>
|
||||
public DateTime LastAccess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Expires attribute indicates the maximum lifetime of the cookie, represented as the date and time at which the cookie expires.
|
||||
/// The user agent is not required to retain the cookie until the specified date has passed.
|
||||
/// In fact, user agents often evict cookies due to memory pressure or privacy concerns.
|
||||
/// </summary>
|
||||
public DateTime Expires { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Max-Age attribute indicates the maximum lifetime of the cookie, represented as the number of seconds until the cookie expires.
|
||||
/// The user agent is not required to retain the cookie for the specified duration.
|
||||
/// In fact, user agents often evict cookies due to memory pressure or privacy concerns.
|
||||
/// </summary>
|
||||
public long MaxAge { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If a cookie has neither the Max-Age nor the Expires attribute, the user agent will retain the cookie until "the current session is over".
|
||||
/// </summary>
|
||||
public bool IsSession { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Domain attribute specifies those hosts to which the cookie will be sent.
|
||||
/// For example, if the value of the Domain attribute is "example.com", the user agent will include the cookie
|
||||
/// in the Cookie header when making HTTP requests to example.com, www.example.com, and www.corp.example.com.
|
||||
/// If the server omits the Domain attribute, the user agent will return the cookie only to the origin server.
|
||||
/// </summary>
|
||||
public string Domain { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The scope of each cookie is limited to a set of paths, controlled by the Path attribute.
|
||||
/// If the server omits the Path attribute, the user agent will use the "directory" of the request-uri's path component as the default value.
|
||||
/// </summary>
|
||||
public string Path { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Secure attribute limits the scope of the cookie to "secure" channels (where "secure" is defined by the user agent).
|
||||
/// When a cookie has the Secure attribute, the user agent will include the cookie in an HTTP request only if the request is
|
||||
/// transmitted over a secure channel (typically HTTP over Transport Layer Security (TLS)).
|
||||
/// </summary>
|
||||
public bool IsSecure { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HttpOnly attribute limits the scope of the cookie to HTTP requests.
|
||||
/// In particular, the attribute instructs the user agent to omit the cookie when providing access to
|
||||
/// cookies via "non-HTTP" APIs (such as a web browser API that exposes cookies to scripts).
|
||||
/// </summary>
|
||||
public bool IsHttpOnly { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SameSite prevents the browser from sending this cookie along with cross-site requests.
|
||||
/// The main goal is mitigate the risk of cross-origin information leakage.
|
||||
/// It also provides some protection against cross-site request forgery attacks. Possible values for the flag are lax or strict.
|
||||
/// <seealso cref="https://web.dev/samesite-cookies-explained/"/>
|
||||
/// </summary>
|
||||
public string SameSite { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
public Cookie(string name, string value)
|
||||
:this(name, value, "/", string.Empty)
|
||||
{}
|
||||
|
||||
public Cookie(string name, string value, string path)
|
||||
: this(name, value, path, string.Empty)
|
||||
{}
|
||||
|
||||
public Cookie(string name, string value, string path, string domain)
|
||||
:this() // call the parameter-less constructor to set default values
|
||||
{
|
||||
this.Name = name;
|
||||
this.Value = value;
|
||||
this.Path = path;
|
||||
this.Domain = domain;
|
||||
}
|
||||
|
||||
public Cookie(Uri uri, string name, string value, DateTime expires, bool isSession = true)
|
||||
:this(name, value, uri.AbsolutePath, uri.Host)
|
||||
{
|
||||
this.Expires = expires;
|
||||
this.IsSession = isSession;
|
||||
this.Date = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public Cookie(Uri uri, string name, string value, long maxAge = -1, bool isSession = true)
|
||||
:this(name, value, uri.AbsolutePath, uri.Host)
|
||||
{
|
||||
this.MaxAge = maxAge;
|
||||
this.IsSession = isSession;
|
||||
this.Date = DateTime.UtcNow;
|
||||
this.SameSite = "none";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal Cookie()
|
||||
{
|
||||
// If a cookie has neither the Max-Age nor the Expires attribute, the user agent will retain the cookie
|
||||
// until "the current session is over" (as defined by the user agent).
|
||||
IsSession = true;
|
||||
MaxAge = -1;
|
||||
LastAccess = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public bool WillExpireInTheFuture()
|
||||
{
|
||||
// No Expires or Max-Age value sent from the server, we will fake the return value so we will not delete the newly came Cookie
|
||||
if (IsSession)
|
||||
return true;
|
||||
|
||||
// If a cookie has both the Max-Age and the Expires attribute, the Max-Age attribute has precedence and controls the expiration date of the cookie.
|
||||
return MaxAge != -1 ?
|
||||
Math.Max(0, (long)(DateTime.UtcNow - Date).TotalSeconds) < MaxAge :
|
||||
Expires > DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Guess the storage size of the cookie.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public uint GuessSize()
|
||||
{
|
||||
return (uint)((this.Name != null ? this.Name.Length * sizeof(char) : 0) +
|
||||
(this.Value != null ? this.Value.Length * sizeof(char) : 0) +
|
||||
(this.Domain != null ? this.Domain.Length * sizeof(char) : 0) +
|
||||
(this.Path != null ? this.Path.Length * sizeof(char) : 0) +
|
||||
(this.SameSite != null ? this.SameSite.Length * sizeof(char) : 0) +
|
||||
(sizeof(long) * 4) +
|
||||
(sizeof(bool) * 3));
|
||||
}
|
||||
|
||||
public static Cookie Parse(string header, Uri defaultDomain, Logger.LoggingContext context)
|
||||
{
|
||||
Cookie cookie = new Cookie();
|
||||
try
|
||||
{
|
||||
var kvps = ParseCookieHeader(header);
|
||||
|
||||
foreach (var kvp in kvps)
|
||||
{
|
||||
switch (kvp.Key.ToLowerInvariant())
|
||||
{
|
||||
case "path":
|
||||
// If the attribute-value is empty or if the first character of the attribute-value is not %x2F ("/"):
|
||||
// Let cookie-path be the default-path.
|
||||
cookie.Path = string.IsNullOrEmpty(kvp.Value) || !kvp.Value.StartsWith("/") ? "/" : cookie.Path = kvp.Value;
|
||||
break;
|
||||
|
||||
case "domain":
|
||||
// If the attribute-value is empty, the behavior is undefined. However, the user agent SHOULD ignore the cookie-av entirely.
|
||||
if (string.IsNullOrEmpty(kvp.Value))
|
||||
return null;
|
||||
|
||||
// If the first character of the attribute-value string is %x2E ("."):
|
||||
// Let cookie-domain be the attribute-value without the leading %x2E (".") character.
|
||||
cookie.Domain = kvp.Value.StartsWith(".") ? kvp.Value.Substring(1) : kvp.Value;
|
||||
break;
|
||||
|
||||
case "expires":
|
||||
cookie.Expires = kvp.Value.ToDateTime(DateTime.FromBinary(0));
|
||||
cookie.IsSession = false;
|
||||
break;
|
||||
|
||||
case "max-age":
|
||||
cookie.MaxAge = kvp.Value.ToInt64(-1);
|
||||
cookie.IsSession = false;
|
||||
break;
|
||||
|
||||
case "secure":
|
||||
cookie.IsSecure = true;
|
||||
break;
|
||||
|
||||
case "httponly":
|
||||
cookie.IsHttpOnly = true;
|
||||
break;
|
||||
|
||||
case "samesite":
|
||||
cookie.SameSite = kvp.Value;
|
||||
break;
|
||||
|
||||
default:
|
||||
// check whether name is already set to avoid overwriting it with a non-listed setting
|
||||
if (string.IsNullOrEmpty(cookie.Name))
|
||||
{
|
||||
cookie.Name = kvp.Key;
|
||||
cookie.Value = kvp.Value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Some user agents provide users the option of preventing persistent storage of cookies across sessions.
|
||||
// When configured thusly, user agents MUST treat all received cookies as if the persistent-flag were set to false.
|
||||
if (HTTPManager.EnablePrivateBrowsing)
|
||||
cookie.IsSession = true;
|
||||
|
||||
// http://tools.ietf.org/html/rfc6265#section-4.1.2.3
|
||||
// WARNING: Some existing user agents treat an absent Domain attribute as if the Domain attribute were present and contained the current host name.
|
||||
// For example, if example.com returns a Set-Cookie header without a Domain attribute, these user agents will erroneously send the cookie to www.example.com as well.
|
||||
if (string.IsNullOrEmpty(cookie.Domain))
|
||||
cookie.Domain = defaultDomain.Host;
|
||||
|
||||
// http://tools.ietf.org/html/rfc6265#section-5.3 section 7:
|
||||
// If the cookie-attribute-list contains an attribute with an attribute-name of "Path",
|
||||
// set the cookie's path to attribute-value of the last attribute in the cookie-attribute-list with an attribute-name of "Path".
|
||||
// __Otherwise, set the cookie's path to the default-path of the request-uri.__
|
||||
if (string.IsNullOrEmpty(cookie.Path))
|
||||
cookie.Path = defaultDomain.AbsolutePath;
|
||||
|
||||
cookie.Date = cookie.LastAccess = DateTime.UtcNow;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Warning("Cookie", "Parse - Couldn't parse header: " + header + " exception: " + ex.ToString() + " " + ex.StackTrace, context);
|
||||
}
|
||||
return cookie;
|
||||
}
|
||||
|
||||
#region Save & Load
|
||||
|
||||
internal void SaveTo(BinaryWriter stream)
|
||||
{
|
||||
stream.Write(Version);
|
||||
stream.Write(Name ?? string.Empty);
|
||||
stream.Write(Value ?? string.Empty);
|
||||
stream.Write(Date.ToBinary());
|
||||
stream.Write(LastAccess.ToBinary());
|
||||
stream.Write(Expires.ToBinary());
|
||||
stream.Write(MaxAge);
|
||||
stream.Write(IsSession);
|
||||
stream.Write(Domain ?? string.Empty);
|
||||
stream.Write(Path ?? string.Empty);
|
||||
stream.Write(IsSecure);
|
||||
stream.Write(IsHttpOnly);
|
||||
}
|
||||
|
||||
internal void LoadFrom(BinaryReader stream)
|
||||
{
|
||||
/*int version = */stream.ReadInt32();
|
||||
this.Name = stream.ReadString();
|
||||
this.Value = stream.ReadString();
|
||||
this.Date = DateTime.FromBinary(stream.ReadInt64());
|
||||
this.LastAccess = DateTime.FromBinary(stream.ReadInt64());
|
||||
this.Expires = DateTime.FromBinary(stream.ReadInt64());
|
||||
this.MaxAge = stream.ReadInt64();
|
||||
this.IsSession = stream.ReadBoolean();
|
||||
this.Domain = stream.ReadString();
|
||||
this.Path = stream.ReadString();
|
||||
this.IsSecure = stream.ReadBoolean();
|
||||
this.IsHttpOnly = stream.ReadBoolean();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides and new Equals function
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Concat(this.Name, "=", this.Value);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
return this.Equals(obj as Cookie);
|
||||
}
|
||||
|
||||
public bool Equals(Cookie cookie)
|
||||
{
|
||||
if (cookie == null)
|
||||
return false;
|
||||
|
||||
if (Object.ReferenceEquals(this, cookie))
|
||||
return true;
|
||||
|
||||
return this.Name.Equals(cookie.Name, StringComparison.Ordinal) &&
|
||||
((this.Domain == null && cookie.Domain == null) || this.Domain.Equals(cookie.Domain, StringComparison.Ordinal)) &&
|
||||
((this.Path == null && cookie.Path == null) || this.Path.Equals(cookie.Path, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.ToString().GetHashCode();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helper Functions
|
||||
|
||||
private static string ReadValue(string str, ref int pos)
|
||||
{
|
||||
string result = string.Empty;
|
||||
if (str == null)
|
||||
return result;
|
||||
|
||||
return str.Read(ref pos, ';');
|
||||
}
|
||||
|
||||
private static List<HeaderValue> ParseCookieHeader(string str)
|
||||
{
|
||||
List<HeaderValue> result = new List<HeaderValue>();
|
||||
|
||||
if (str == null)
|
||||
return result;
|
||||
|
||||
int idx = 0;
|
||||
|
||||
// process the rest of the text
|
||||
while (idx < str.Length)
|
||||
{
|
||||
// Read key
|
||||
string key = str.Read(ref idx, (ch) => ch != '=' && ch != ';').Trim();
|
||||
HeaderValue qp = new HeaderValue(key);
|
||||
|
||||
if (idx < str.Length && str[idx - 1] == '=')
|
||||
qp.Value = ReadValue(str, ref idx);
|
||||
|
||||
result.Add(qp);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IComparable<Cookie> implementation
|
||||
|
||||
public int CompareTo(Cookie other)
|
||||
{
|
||||
return this.LastAccess.CompareTo(other.LastAccess);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,538 @@
|
|||
#if !BESTHTTP_DISABLE_COOKIES
|
||||
|
||||
using BestHTTP.Core;
|
||||
using BestHTTP.PlatformSupport.FileSystem;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace BestHTTP.Cookies
|
||||
{
|
||||
/// <summary>
|
||||
/// The Cookie Jar implementation based on RFC 6265(http://tools.ietf.org/html/rfc6265).
|
||||
/// </summary>
|
||||
public static class CookieJar
|
||||
{
|
||||
// Version of the cookie store. It may be used in a future version for maintaining compatibility.
|
||||
private const int Version = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if File apis are supported.
|
||||
/// </summary>
|
||||
public static bool IsSavingSupported
|
||||
{
|
||||
get
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_COOKIE_SAVE
|
||||
if (IsSupportCheckDone)
|
||||
return _isSavingSupported;
|
||||
|
||||
try
|
||||
{
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
_isSavingSupported = false;
|
||||
#else
|
||||
HTTPManager.IOService.DirectoryExists(HTTPManager.GetRootCacheFolder());
|
||||
_isSavingSupported = true;
|
||||
#endif
|
||||
}
|
||||
catch
|
||||
{
|
||||
_isSavingSupported = false;
|
||||
|
||||
HTTPManager.Logger.Warning("CookieJar", "Cookie saving and loading disabled!");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsSupportCheckDone = true;
|
||||
}
|
||||
|
||||
return _isSavingSupported;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The plugin will delete cookies that are accessed this threshold ago. Its default value is 7 days.
|
||||
/// </summary>
|
||||
public static TimeSpan AccessThreshold = TimeSpan.FromDays(7);
|
||||
|
||||
#region Privates
|
||||
|
||||
/// <summary>
|
||||
/// List of the Cookies
|
||||
/// </summary>
|
||||
private static List<Cookie> Cookies = new List<Cookie>();
|
||||
private static string CookieFolder { get; set; }
|
||||
private static string LibraryPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Synchronization object for thread safety.
|
||||
/// </summary>
|
||||
private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
||||
|
||||
#if !BESTHTTP_DISABLE_COOKIE_SAVE
|
||||
private static bool _isSavingSupported;
|
||||
private static bool IsSupportCheckDone;
|
||||
#endif
|
||||
|
||||
private static bool Loaded;
|
||||
#endregion
|
||||
|
||||
#region Internal Functions
|
||||
|
||||
internal static void SetupFolder()
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_COOKIE_SAVE
|
||||
if (!CookieJar.IsSavingSupported)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(CookieFolder) || string.IsNullOrEmpty(LibraryPath))
|
||||
{
|
||||
CookieFolder = System.IO.Path.Combine(HTTPManager.GetRootCacheFolder(), "Cookies");
|
||||
LibraryPath = System.IO.Path.Combine(CookieFolder, "Library");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will set or update all cookies from the response object.
|
||||
/// </summary>
|
||||
internal static bool Set(HTTPResponse response)
|
||||
{
|
||||
if (response == null)
|
||||
return false;
|
||||
|
||||
List<Cookie> newCookies = new List<Cookie>();
|
||||
var setCookieHeaders = response.GetHeaderValues("set-cookie");
|
||||
|
||||
// No cookies. :'(
|
||||
if (setCookieHeaders == null)
|
||||
return false;
|
||||
|
||||
foreach (var cookieHeader in setCookieHeaders)
|
||||
{
|
||||
Cookie cookie = Cookie.Parse(cookieHeader, response.baseRequest.CurrentUri, response.baseRequest.Context);
|
||||
if (cookie != null)
|
||||
{
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
int idx;
|
||||
var old = Find(cookie, out idx);
|
||||
|
||||
// if no value for the cookie or already expired then the server asked us to delete the cookie
|
||||
bool expired = string.IsNullOrEmpty(cookie.Value) || !cookie.WillExpireInTheFuture();
|
||||
|
||||
if (!expired)
|
||||
{
|
||||
// no old cookie, add it straight to the list
|
||||
if (old == null)
|
||||
{
|
||||
Cookies.Add(cookie);
|
||||
|
||||
newCookies.Add(cookie);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the creation-time of the newly created cookie to match the creation-time of the old-cookie.
|
||||
cookie.Date = old.Date;
|
||||
Cookies[idx] = cookie;
|
||||
|
||||
newCookies.Add(cookie);
|
||||
}
|
||||
}
|
||||
else if (idx != -1) // delete the cookie
|
||||
Cookies.RemoveAt(idx);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore cookie on error
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response.Cookies = newCookies;
|
||||
|
||||
PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all expired or 'old' cookies, and will keep the sum size of cookies under the given size.
|
||||
/// </summary>
|
||||
internal static void Maintain(bool sendEvent)
|
||||
{
|
||||
// It's not the same as in the rfc:
|
||||
// http://tools.ietf.org/html/rfc6265#section-5.3
|
||||
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
uint size = 0;
|
||||
|
||||
for (int i = 0; i < Cookies.Count; )
|
||||
{
|
||||
var cookie = Cookies[i];
|
||||
|
||||
// Remove expired or not used cookies
|
||||
if (!cookie.WillExpireInTheFuture() || (cookie.LastAccess + AccessThreshold) < DateTime.UtcNow)
|
||||
{
|
||||
Cookies.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!cookie.IsSession)
|
||||
size += cookie.GuessSize();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (size > HTTPManager.CookieJarSize)
|
||||
{
|
||||
Cookies.Sort();
|
||||
|
||||
while (size > HTTPManager.CookieJarSize && Cookies.Count > 0)
|
||||
{
|
||||
var cookie = Cookies[0];
|
||||
Cookies.RemoveAt(0);
|
||||
|
||||
size -= cookie.GuessSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
if (sendEvent)
|
||||
PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the Cookie Jar to a file.
|
||||
/// </summary>
|
||||
/// <remarks>Not implemented under Unity WebPlayer</remarks>
|
||||
internal static void Persist()
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_COOKIE_SAVE
|
||||
if (!IsSavingSupported)
|
||||
return;
|
||||
|
||||
if (!Loaded)
|
||||
return;
|
||||
|
||||
// Delete any expired cookie
|
||||
Maintain(false);
|
||||
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (!HTTPManager.IOService.DirectoryExists(CookieFolder))
|
||||
HTTPManager.IOService.DirectoryCreate(CookieFolder);
|
||||
|
||||
using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.Create))
|
||||
using (var bw = new System.IO.BinaryWriter(fs))
|
||||
{
|
||||
bw.Write(Version);
|
||||
|
||||
// Count how many non-session cookies we have
|
||||
int count = 0;
|
||||
foreach (var cookie in Cookies)
|
||||
if (!cookie.IsSession)
|
||||
count++;
|
||||
|
||||
bw.Write(count);
|
||||
|
||||
// Save only the persistable cookies
|
||||
foreach (var cookie in Cookies)
|
||||
if (!cookie.IsSession)
|
||||
cookie.SaveTo(bw);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load previously persisted cookie library from the file.
|
||||
/// </summary>
|
||||
internal static void Load()
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_COOKIE_SAVE
|
||||
if (!IsSavingSupported)
|
||||
return;
|
||||
|
||||
if (Loaded)
|
||||
return;
|
||||
|
||||
SetupFolder();
|
||||
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
Cookies.Clear();
|
||||
|
||||
if (!HTTPManager.IOService.DirectoryExists(CookieFolder))
|
||||
HTTPManager.IOService.DirectoryCreate(CookieFolder);
|
||||
|
||||
if (!HTTPManager.IOService.FileExists(LibraryPath))
|
||||
return;
|
||||
|
||||
using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.OpenRead))
|
||||
using (var br = new System.IO.BinaryReader(fs))
|
||||
{
|
||||
/*int version = */br.ReadInt32();
|
||||
int cookieCount = br.ReadInt32();
|
||||
|
||||
for (int i = 0; i < cookieCount; ++i)
|
||||
{
|
||||
Cookie cookie = new Cookie();
|
||||
cookie.LoadFrom(br);
|
||||
|
||||
if (cookie.WillExpireInTheFuture())
|
||||
Cookies.Add(cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Cookies.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Loaded = true;
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Returns all Cookies that corresponds to the given Uri.
|
||||
/// </summary>
|
||||
public static List<Cookie> Get(Uri uri)
|
||||
{
|
||||
Load();
|
||||
|
||||
rwLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
List<Cookie> result = null;
|
||||
|
||||
for (int i = 0; i < Cookies.Count; ++i)
|
||||
{
|
||||
Cookie cookie = Cookies[i];
|
||||
if (cookie.WillExpireInTheFuture() && (uri.Host.IndexOf(cookie.Domain) != -1 || string.Format("{0}:{1}", uri.Host, uri.Port).IndexOf(cookie.Domain) != -1) && uri.AbsolutePath.StartsWith(cookie.Path))
|
||||
{
|
||||
if (result == null)
|
||||
result = new List<Cookie>();
|
||||
|
||||
result.Add(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will add a new, or overwrite an old cookie if already exists.
|
||||
/// </summary>
|
||||
public static void Set(Uri uri, Cookie cookie)
|
||||
{
|
||||
Set(cookie);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will add a new, or overwrite an old cookie if already exists.
|
||||
/// </summary>
|
||||
public static void Set(Cookie cookie)
|
||||
{
|
||||
Load();
|
||||
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
int idx;
|
||||
Find(cookie, out idx);
|
||||
|
||||
if (idx >= 0)
|
||||
Cookies[idx] = cookie;
|
||||
else
|
||||
Cookies.Add(cookie);
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
|
||||
}
|
||||
|
||||
public static List<Cookie> GetAll()
|
||||
{
|
||||
Load();
|
||||
|
||||
return Cookies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all cookies from the Jar.
|
||||
/// </summary>
|
||||
public static void Clear()
|
||||
{
|
||||
Load();
|
||||
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
Cookies.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
Persist();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes cookies that older than the given parameter.
|
||||
/// </summary>
|
||||
public static void Clear(TimeSpan olderThan)
|
||||
{
|
||||
Load();
|
||||
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < Cookies.Count; )
|
||||
{
|
||||
var cookie = Cookies[i];
|
||||
|
||||
// Remove expired or not used cookies
|
||||
if (!cookie.WillExpireInTheFuture() || (cookie.Date + olderThan) < DateTime.UtcNow)
|
||||
Cookies.RemoveAt(i);
|
||||
else
|
||||
i++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
Persist();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes cookies that matches to the given domain.
|
||||
/// </summary>
|
||||
public static void Clear(string domain)
|
||||
{
|
||||
Load();
|
||||
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < Cookies.Count; )
|
||||
{
|
||||
var cookie = Cookies[i];
|
||||
|
||||
// Remove expired or not used cookies
|
||||
if (!cookie.WillExpireInTheFuture() || cookie.Domain.IndexOf(domain) != -1)
|
||||
Cookies.RemoveAt(i);
|
||||
else
|
||||
i++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
Persist();
|
||||
}
|
||||
|
||||
public static void Remove(Uri uri, string name)
|
||||
{
|
||||
Load();
|
||||
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < Cookies.Count; )
|
||||
{
|
||||
var cookie = Cookies[i];
|
||||
|
||||
if (cookie.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && uri.Host.IndexOf(cookie.Domain) != -1)
|
||||
Cookies.RemoveAt(i);
|
||||
else
|
||||
i++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helper Functions
|
||||
|
||||
/// <summary>
|
||||
/// Find and return a Cookie and his index in the list.
|
||||
/// </summary>
|
||||
private static Cookie Find(Cookie cookie, out int idx)
|
||||
{
|
||||
for (int i = 0; i < Cookies.Count; ++i)
|
||||
{
|
||||
Cookie c = Cookies[i];
|
||||
|
||||
if (c.Equals(cookie))
|
||||
{
|
||||
idx = i;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
idx = -1;
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using BestHTTP.Connections;
|
||||
using BestHTTP.Logger;
|
||||
|
||||
// Required for ConcurrentQueue.Clear extension.
|
||||
using BestHTTP.Extensions;
|
||||
|
||||
namespace BestHTTP.Core
|
||||
{
|
||||
public enum ConnectionEvents
|
||||
{
|
||||
StateChange,
|
||||
ProtocolSupport
|
||||
}
|
||||
|
||||
public
|
||||
#if CSHARP_7_OR_LATER
|
||||
readonly
|
||||
#endif
|
||||
struct ConnectionEventInfo
|
||||
{
|
||||
public readonly ConnectionBase Source;
|
||||
|
||||
public readonly ConnectionEvents Event;
|
||||
|
||||
public readonly HTTPConnectionStates State;
|
||||
|
||||
public readonly HostProtocolSupport ProtocolSupport;
|
||||
|
||||
public readonly HTTPRequest Request;
|
||||
|
||||
public ConnectionEventInfo(ConnectionBase sourceConn, ConnectionEvents @event)
|
||||
{
|
||||
this.Source = sourceConn;
|
||||
this.Event = @event;
|
||||
|
||||
this.State = HTTPConnectionStates.Initial;
|
||||
|
||||
this.ProtocolSupport = HostProtocolSupport.Unknown;
|
||||
|
||||
this.Request = null;
|
||||
}
|
||||
|
||||
public ConnectionEventInfo(ConnectionBase sourceConn, HTTPConnectionStates newState)
|
||||
{
|
||||
this.Source = sourceConn;
|
||||
|
||||
this.Event = ConnectionEvents.StateChange;
|
||||
|
||||
this.State = newState;
|
||||
|
||||
this.ProtocolSupport = HostProtocolSupport.Unknown;
|
||||
|
||||
this.Request = null;
|
||||
}
|
||||
|
||||
public ConnectionEventInfo(ConnectionBase sourceConn, HostProtocolSupport protocolSupport)
|
||||
{
|
||||
this.Source = sourceConn;
|
||||
this.Event = ConnectionEvents.ProtocolSupport;
|
||||
|
||||
this.State = HTTPConnectionStates.Initial;
|
||||
|
||||
this.ProtocolSupport = protocolSupport;
|
||||
|
||||
this.Request = null;
|
||||
}
|
||||
|
||||
public ConnectionEventInfo(ConnectionBase sourceConn, HTTPRequest request)
|
||||
{
|
||||
this.Source = sourceConn;
|
||||
|
||||
this.Event = ConnectionEvents.StateChange;
|
||||
|
||||
this.State = HTTPConnectionStates.ClosedResendRequest;
|
||||
|
||||
this.ProtocolSupport = HostProtocolSupport.Unknown;
|
||||
|
||||
this.Request = request;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ConnectionEventInfo SourceConnection: {0}, Event: {1}, State: {2}, ProtocolSupport: {3}]",
|
||||
this.Source.ToString(), this.Event, this.State, this.ProtocolSupport);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConnectionEventHelper
|
||||
{
|
||||
private static ConcurrentQueue<ConnectionEventInfo> connectionEventQueue = new ConcurrentQueue<ConnectionEventInfo>();
|
||||
|
||||
#pragma warning disable 0649
|
||||
public static Action<ConnectionEventInfo> OnEvent;
|
||||
#pragma warning restore
|
||||
|
||||
public static void EnqueueConnectionEvent(ConnectionEventInfo @event)
|
||||
{
|
||||
if (HTTPManager.Logger.Level == Loglevels.All)
|
||||
HTTPManager.Logger.Information("ConnectionEventHelper", "Enqueue connection event: " + @event.ToString(), @event.Source.Context);
|
||||
|
||||
connectionEventQueue.Enqueue(@event);
|
||||
}
|
||||
|
||||
internal static void Clear()
|
||||
{
|
||||
connectionEventQueue.Clear();
|
||||
}
|
||||
|
||||
internal static void ProcessQueue()
|
||||
{
|
||||
ConnectionEventInfo connectionEvent;
|
||||
while (connectionEventQueue.TryDequeue(out connectionEvent))
|
||||
{
|
||||
if (HTTPManager.Logger.Level == Loglevels.All)
|
||||
HTTPManager.Logger.Information("ConnectionEventHelper", "Processing connection event: " + connectionEvent.ToString(), connectionEvent.Source.Context);
|
||||
|
||||
if (OnEvent != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnEvent(connectionEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("ConnectionEventHelper", "ProcessQueue", ex, connectionEvent.Source.Context);
|
||||
}
|
||||
}
|
||||
|
||||
if (connectionEvent.Source.LastProcessedUri == null)
|
||||
{
|
||||
HTTPManager.Logger.Information("ConnectionEventHelper", String.Format("Ignoring ConnectionEventInfo({0}) because its LastProcessedUri is null!", connectionEvent.ToString()), connectionEvent.Source.Context);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (connectionEvent.Event)
|
||||
{
|
||||
case ConnectionEvents.StateChange:
|
||||
HandleConnectionStateChange(connectionEvent);
|
||||
break;
|
||||
|
||||
case ConnectionEvents.ProtocolSupport:
|
||||
HostManager.GetHost(connectionEvent.Source.LastProcessedUri.Host)
|
||||
.GetHostDefinition(connectionEvent.Source.ServerAddress)
|
||||
.AddProtocol(connectionEvent.ProtocolSupport);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleConnectionStateChange(ConnectionEventInfo @event)
|
||||
{
|
||||
try
|
||||
{
|
||||
var connection = @event.Source;
|
||||
|
||||
switch (@event.State)
|
||||
{
|
||||
case HTTPConnectionStates.Recycle:
|
||||
HostManager.GetHost(connection.LastProcessedUri.Host)
|
||||
.GetHostDefinition(connection.ServerAddress)
|
||||
.RecycleConnection(connection)
|
||||
.TryToSendQueuedRequests();
|
||||
|
||||
break;
|
||||
|
||||
case HTTPConnectionStates.WaitForProtocolShutdown:
|
||||
HostManager.GetHost(connection.LastProcessedUri.Host)
|
||||
.GetHostDefinition(connection.ServerAddress)
|
||||
.RemoveConnection(connection, @event.State);
|
||||
break;
|
||||
|
||||
case HTTPConnectionStates.Closed:
|
||||
case HTTPConnectionStates.ClosedResendRequest:
|
||||
// in case of ClosedResendRequest
|
||||
if (@event.Request != null)
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(@event.Request, RequestEvents.Resend));
|
||||
|
||||
HostManager.GetHost(connection.LastProcessedUri.Host)
|
||||
.GetHostDefinition(connection.ServerAddress)
|
||||
.RemoveConnection(connection, @event.State)
|
||||
.TryToSendQueuedRequests();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("ConnectionEvents", $"HandleConnectionStateChange ({@event.State})", ex, @event.Source.Context);
|
||||
UnityEngine.Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BestHTTP.Connections;
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.Logger;
|
||||
|
||||
namespace BestHTTP.Core
|
||||
{
|
||||
public enum HostProtocolSupport : byte
|
||||
{
|
||||
Unknown = 0x00,
|
||||
HTTP1 = 0x01,
|
||||
HTTP2 = 0x02
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A HostConnection object manages the connections to a host and the request queue.
|
||||
/// </summary>
|
||||
public sealed class HostConnection
|
||||
{
|
||||
public HostDefinition Host { get; private set; }
|
||||
|
||||
public string VariantId { get; private set; }
|
||||
|
||||
public HostProtocolSupport ProtocolSupport { get; private set; }
|
||||
public DateTime LastProtocolSupportUpdate { get; private set; }
|
||||
|
||||
public int QueuedRequests { get { return this.Queue.Count; } }
|
||||
|
||||
public LoggingContext Context { get; private set; }
|
||||
|
||||
private List<ConnectionBase> Connections = new List<ConnectionBase>();
|
||||
private List<HTTPRequest> Queue = new List<HTTPRequest>();
|
||||
|
||||
public HostConnection(HostDefinition host, string variantId)
|
||||
{
|
||||
this.Host = host;
|
||||
this.VariantId = variantId;
|
||||
|
||||
this.Context = new LoggingContext(this);
|
||||
this.Context.Add("Host", this.Host.Host);
|
||||
this.Context.Add("VariantId", this.VariantId);
|
||||
}
|
||||
|
||||
internal void AddProtocol(HostProtocolSupport protocolSupport)
|
||||
{
|
||||
this.LastProtocolSupportUpdate = DateTime.UtcNow;
|
||||
|
||||
var oldProtocol = this.ProtocolSupport;
|
||||
|
||||
if (oldProtocol != protocolSupport)
|
||||
{
|
||||
this.ProtocolSupport = protocolSupport;
|
||||
|
||||
HTTPManager.Logger.Information(typeof(HostConnection).Name, string.Format("AddProtocol({0}) - changing from {1} to {2}", this.VariantId, oldProtocol, protocolSupport), this.Context);
|
||||
|
||||
HostManager.Save();
|
||||
}
|
||||
|
||||
if (protocolSupport == HostProtocolSupport.HTTP2)
|
||||
TryToSendQueuedRequests();
|
||||
}
|
||||
|
||||
internal HostConnection Send(HTTPRequest request)
|
||||
{
|
||||
var conn = GetNextAvailable(request);
|
||||
|
||||
if (conn != null)
|
||||
{
|
||||
request.State = HTTPRequestStates.Processing;
|
||||
|
||||
request.Prepare();
|
||||
|
||||
// then start process the request
|
||||
conn.Process(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no free connection found and creation prohibited, we will put back to the queue
|
||||
this.Queue.Add(request);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
internal ConnectionBase GetNextAvailable(HTTPRequest request)
|
||||
{
|
||||
int activeConnections = 0;
|
||||
ConnectionBase conn = null;
|
||||
// Check the last created connection first. This way, if a higher level protocol is present that can handle more requests (== HTTP/2) that protocol will be chosen
|
||||
// and others will be closed when their inactivity time is reached.
|
||||
for (int i = Connections.Count - 1; i >= 0; --i)
|
||||
{
|
||||
conn = Connections[i];
|
||||
|
||||
if (conn.State == HTTPConnectionStates.Initial || conn.State == HTTPConnectionStates.Free || conn.CanProcessMultiple)
|
||||
{
|
||||
if (!conn.TestConnection())
|
||||
{
|
||||
HTTPManager.Logger.Verbose("HostConnection", "GetNextAvailable - TestConnection returned false!", this.Context, request.Context, conn.Context);
|
||||
|
||||
RemoveConnectionImpl(conn, HTTPConnectionStates.Closed);
|
||||
continue;
|
||||
}
|
||||
|
||||
HTTPManager.Logger.Verbose("HostConnection", string.Format("GetNextAvailable - returning with connection. state: {0}, CanProcessMultiple: {1}", conn.State, conn.CanProcessMultiple), this.Context, request.Context, conn.Context);
|
||||
return conn;
|
||||
}
|
||||
|
||||
activeConnections++;
|
||||
}
|
||||
|
||||
if (activeConnections >= HTTPManager.MaxConnectionPerServer)
|
||||
{
|
||||
HTTPManager.Logger.Verbose("HostConnection", string.Format("GetNextAvailable - activeConnections({0}) >= HTTPManager.MaxConnectionPerServer({1})", activeConnections, HTTPManager.MaxConnectionPerServer), this.Context, request.Context);
|
||||
return null;
|
||||
}
|
||||
|
||||
string key = HostDefinition.GetKeyForRequest(request);
|
||||
|
||||
conn = null;
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
conn = new WebGLConnection(key);
|
||||
#else
|
||||
if (request.CurrentUri.IsFile)
|
||||
conn = new FileConnection(key);
|
||||
else
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
// Hold back the creation of a new connection until we know more about the remote host's features.
|
||||
// If we send out multiple requests at once it will execute the first and delay the others.
|
||||
// While it will decrease performance initially, it will prevent the creation of TCP connections
|
||||
// that will be unused after their first request processing if the server supports HTTP/2.
|
||||
if (activeConnections >= 1 && (this.ProtocolSupport == HostProtocolSupport.Unknown || this.ProtocolSupport == HostProtocolSupport.HTTP2))
|
||||
{
|
||||
HTTPManager.Logger.Verbose("HostConnection", string.Format("GetNextAvailable - waiting for protocol support message. activeConnections: {0}, ProtocolSupport: {1} ", activeConnections, this.ProtocolSupport), this.Context, request.Context);
|
||||
return null;
|
||||
}
|
||||
#endif
|
||||
|
||||
conn = new HTTPConnection(key);
|
||||
HTTPManager.Logger.Verbose("HostConnection", string.Format("GetNextAvailable - creating new connection, key: {0} ", key), this.Context, request.Context, conn.Context);
|
||||
}
|
||||
#endif
|
||||
Connections.Add(conn);
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
internal HostConnection RecycleConnection(ConnectionBase conn)
|
||||
{
|
||||
conn.State = HTTPConnectionStates.Free;
|
||||
|
||||
BestHTTP.Extensions.Timer.Add(new TimerData(TimeSpan.FromSeconds(1), conn, CloseConnectionAfterInactivity));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private bool RemoveConnectionImpl(ConnectionBase conn, HTTPConnectionStates setState)
|
||||
{
|
||||
conn.State = setState;
|
||||
conn.Dispose();
|
||||
|
||||
bool found = this.Connections.Remove(conn);
|
||||
|
||||
if (!found)
|
||||
HTTPManager.Logger.Information(typeof(HostConnection).Name, string.Format("RemoveConnection - Couldn't find connection! key: {0}", conn.ServerAddress), this.Context, conn.Context);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
internal HostConnection RemoveConnection(ConnectionBase conn, HTTPConnectionStates setState)
|
||||
{
|
||||
RemoveConnectionImpl(conn, setState);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
internal HostConnection TryToSendQueuedRequests()
|
||||
{
|
||||
while (this.Queue.Count > 0 && GetNextAvailable(this.Queue[0]) != null)
|
||||
{
|
||||
Send(this.Queue[0]);
|
||||
this.Queue.RemoveAt(0);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConnectionBase Find(Predicate<ConnectionBase> match)
|
||||
{
|
||||
return this.Connections.Find(match);
|
||||
}
|
||||
|
||||
private bool CloseConnectionAfterInactivity(DateTime now, object context)
|
||||
{
|
||||
var conn = context as ConnectionBase;
|
||||
|
||||
bool closeConnection = conn.State == HTTPConnectionStates.Free && now - conn.LastProcessTime >= conn.KeepAliveTime;
|
||||
if (closeConnection)
|
||||
{
|
||||
HTTPManager.Logger.Information(typeof(HostConnection).Name, string.Format("CloseConnectionAfterInactivity - [{0}] Closing! State: {1}, Now: {2}, LastProcessTime: {3}, KeepAliveTime: {4}",
|
||||
conn.ToString(), conn.State, now.ToString(System.Globalization.CultureInfo.InvariantCulture), conn.LastProcessTime.ToString(System.Globalization.CultureInfo.InvariantCulture), conn.KeepAliveTime), this.Context, conn.Context);
|
||||
|
||||
RemoveConnection(conn, HTTPConnectionStates.Closed);
|
||||
return false;
|
||||
}
|
||||
|
||||
// repeat until the connection's state is free
|
||||
return conn.State == HTTPConnectionStates.Free;
|
||||
}
|
||||
|
||||
public void RemoveAllIdleConnections()
|
||||
{
|
||||
for (int i = 0; i < this.Connections.Count; i++)
|
||||
if (this.Connections[i].State == HTTPConnectionStates.Free)
|
||||
{
|
||||
int countBefore = this.Connections.Count;
|
||||
RemoveConnection(this.Connections[i], HTTPConnectionStates.Closed);
|
||||
|
||||
if (countBefore != this.Connections.Count)
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Shutdown()
|
||||
{
|
||||
this.Queue.Clear();
|
||||
|
||||
foreach (var conn in this.Connections)
|
||||
{
|
||||
// Swallow any exceptions, we are quitting anyway.
|
||||
try
|
||||
{
|
||||
conn.Shutdown(ShutdownTypes.Immediate);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
//this.Connections.Clear();
|
||||
}
|
||||
|
||||
internal void SaveTo(System.IO.BinaryWriter bw)
|
||||
{
|
||||
bw.Write(this.LastProtocolSupportUpdate.ToBinary());
|
||||
bw.Write((byte)this.ProtocolSupport);
|
||||
}
|
||||
|
||||
internal void LoadFrom(int version, System.IO.BinaryReader br)
|
||||
{
|
||||
this.LastProtocolSupportUpdate = DateTime.FromBinary(br.ReadInt64());
|
||||
this.ProtocolSupport = (HostProtocolSupport)br.ReadByte();
|
||||
|
||||
if (DateTime.UtcNow - this.LastProtocolSupportUpdate >= TimeSpan.FromDays(1))
|
||||
{
|
||||
HTTPManager.Logger.Verbose("HostConnection", string.Format("LoadFrom - Too Old! LastProtocolSupportUpdate: {0}, ProtocolSupport: {1}", this.LastProtocolSupportUpdate.ToString(System.Globalization.CultureInfo.InvariantCulture), this.ProtocolSupport), this.Context);
|
||||
this.ProtocolSupport = HostProtocolSupport.Unknown;
|
||||
}
|
||||
else
|
||||
HTTPManager.Logger.Verbose("HostConnection", string.Format("LoadFrom - LastProtocolSupportUpdate: {0}, ProtocolSupport: {1}", this.LastProtocolSupportUpdate.ToString(System.Globalization.CultureInfo.InvariantCulture), this.ProtocolSupport), this.Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BestHTTP.Connections;
|
||||
using BestHTTP.PlatformSupport.Text;
|
||||
|
||||
namespace BestHTTP.Core
|
||||
{
|
||||
public sealed class HostDefinition
|
||||
{
|
||||
public string Host { get; private set; }
|
||||
|
||||
// alt-svc support:
|
||||
// 1. When a request receives an alt-svc header send a plugin msg to the manager with all the details to route to the proper hostDefinition.
|
||||
// 2. HostDefinition parses the header value
|
||||
// 3. If there's at least one supported protocol found, start open a connection to that said alternate
|
||||
// 4. If the new connection is open, route new requests to that connection
|
||||
public List<HostConnection> Alternates;
|
||||
|
||||
/// <summary>
|
||||
/// Requests to the same host can require different connections: http, https, http + proxy, https + proxy, http2, http2 + proxy
|
||||
/// </summary>
|
||||
public Dictionary<string, HostConnection> hostConnectionVariant = new Dictionary<string, HostConnection>();
|
||||
|
||||
public HostDefinition(string host)
|
||||
{
|
||||
this.Host = host;
|
||||
}
|
||||
|
||||
public HostConnection HasBetterAlternate(HTTPRequest request)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public HostConnection GetHostDefinition(HTTPRequest request)
|
||||
{
|
||||
string key = GetKeyForRequest(request);
|
||||
|
||||
return GetHostDefinition(key);
|
||||
}
|
||||
|
||||
public HostConnection GetHostDefinition(string key)
|
||||
{
|
||||
HostConnection host = null;
|
||||
|
||||
if (!this.hostConnectionVariant.TryGetValue(key, out host))
|
||||
this.hostConnectionVariant.Add(key, host = new HostConnection(this, key));
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
public void Send(HTTPRequest request)
|
||||
{
|
||||
GetHostDefinition(request)
|
||||
.Send(request);
|
||||
}
|
||||
|
||||
public void TryToSendQueuedRequests()
|
||||
{
|
||||
foreach (var kvp in hostConnectionVariant)
|
||||
kvp.Value.TryToSendQueuedRequests();
|
||||
}
|
||||
|
||||
public void HandleAltSvcHeader(HTTPResponse response)
|
||||
{
|
||||
var headerValues = response.GetHeaderValues("alt-svc");
|
||||
if (headerValues == null)
|
||||
HTTPManager.Logger.Warning(typeof(HostDefinition).Name, "Received HandleAltSvcHeader message, but no Alt-Svc header found!", response.Context);
|
||||
}
|
||||
|
||||
public void HandleConnectProtocol(HTTP2ConnectProtocolInfo info)
|
||||
{
|
||||
HTTPManager.Logger.Information(typeof(HostDefinition).Name, string.Format("Received HandleConnectProtocol message. Connect protocol for host {0}. Enabled: {1}", info.Host, info.Enabled));
|
||||
}
|
||||
|
||||
internal void Shutdown()
|
||||
{
|
||||
foreach (var kvp in this.hostConnectionVariant)
|
||||
{
|
||||
kvp.Value.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
internal void SaveTo(System.IO.BinaryWriter bw)
|
||||
{
|
||||
bw.Write(this.hostConnectionVariant.Count);
|
||||
|
||||
foreach (var kvp in this.hostConnectionVariant)
|
||||
{
|
||||
bw.Write(kvp.Key.ToString());
|
||||
|
||||
kvp.Value.SaveTo(bw);
|
||||
}
|
||||
}
|
||||
|
||||
internal void LoadFrom(int version, System.IO.BinaryReader br)
|
||||
{
|
||||
int count = br.ReadInt32();
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
GetHostDefinition(br.ReadString())
|
||||
.LoadFrom(version, br);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetKeyForRequest(HTTPRequest request)
|
||||
{
|
||||
return GetKeyFor(request.CurrentUri
|
||||
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
, request.Proxy
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
public static string GetKeyFor(Uri uri
|
||||
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
, Proxy proxy
|
||||
#endif
|
||||
)
|
||||
{
|
||||
if (uri.IsFile)
|
||||
return uri.ToString();
|
||||
|
||||
var keyBuilder = StringBuilderPool.Get(11);
|
||||
|
||||
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
if (proxy != null && proxy.UseProxyForAddress(uri))
|
||||
{
|
||||
keyBuilder.Append(proxy.Address.Scheme);
|
||||
keyBuilder.Append("://");
|
||||
keyBuilder.Append(proxy.Address.Host);
|
||||
keyBuilder.Append(":");
|
||||
keyBuilder.Append(proxy.Address.Port);
|
||||
keyBuilder.Append(" @ ");
|
||||
}
|
||||
#endif
|
||||
|
||||
keyBuilder.Append(uri.Scheme);
|
||||
keyBuilder.Append("://");
|
||||
keyBuilder.Append(uri.Host);
|
||||
keyBuilder.Append(":");
|
||||
keyBuilder.Append(uri.Port);
|
||||
|
||||
return StringBuilderPool.ReleaseAndGrab(keyBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
using BestHTTP.PlatformSupport.FileSystem;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BestHTTP.Core
|
||||
{
|
||||
public static class HostManager
|
||||
{
|
||||
private const int Version = 1;
|
||||
private static string LibraryPath = string.Empty;
|
||||
private static bool IsSaveAndLoadSupported = false;
|
||||
private static bool IsLoaded = false;
|
||||
|
||||
private static Dictionary<string, HostDefinition> hosts = new Dictionary<string, HostDefinition>();
|
||||
|
||||
public static HostDefinition GetHost(string hostStr)
|
||||
{
|
||||
HostDefinition host;
|
||||
if (!hosts.TryGetValue(hostStr, out host))
|
||||
hosts.Add(hostStr, host = new HostDefinition(hostStr));
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
public static void RemoveAllIdleConnections()
|
||||
{
|
||||
HTTPManager.Logger.Information("HostManager", "RemoveAllIdleConnections");
|
||||
foreach (var host_kvp in hosts)
|
||||
foreach (var variant_kvp in host_kvp.Value.hostConnectionVariant)
|
||||
variant_kvp.Value.RemoveAllIdleConnections();
|
||||
}
|
||||
|
||||
public static void TryToSendQueuedRequests()
|
||||
{
|
||||
foreach (var kvp in hosts)
|
||||
kvp.Value.TryToSendQueuedRequests();
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
HTTPManager.Logger.Information("HostManager", "Shutdown initiated!");
|
||||
foreach (var kvp in hosts)
|
||||
kvp.Value.Shutdown();
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
HTTPManager.Logger.Information("HostManager", "Clearing hosts!");
|
||||
hosts.Clear();
|
||||
}
|
||||
|
||||
private static void SetupFolder()
|
||||
{
|
||||
if (string.IsNullOrEmpty(LibraryPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
LibraryPath = System.IO.Path.Combine(HTTPManager.GetRootCacheFolder(), "Hosts");
|
||||
HTTPManager.IOService.FileExists(LibraryPath);
|
||||
IsSaveAndLoadSupported = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
IsSaveAndLoadSupported = false;
|
||||
HTTPManager.Logger.Warning("HostManager", "Save and load Disabled!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
if (!IsSaveAndLoadSupported || string.IsNullOrEmpty(LibraryPath))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.Create))
|
||||
using (var bw = new System.IO.BinaryWriter(fs))
|
||||
{
|
||||
bw.Write(Version);
|
||||
|
||||
bw.Write(hosts.Count);
|
||||
foreach (var kvp in hosts)
|
||||
{
|
||||
bw.Write(kvp.Key.ToString());
|
||||
|
||||
kvp.Value.SaveTo(bw);
|
||||
}
|
||||
}
|
||||
HTTPManager.Logger.Information("HostManager", hosts.Count + " hosts saved!");
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
|
||||
public static void Load()
|
||||
{
|
||||
if (IsLoaded)
|
||||
return;
|
||||
IsLoaded = true;
|
||||
|
||||
SetupFolder();
|
||||
|
||||
if (!IsSaveAndLoadSupported || string.IsNullOrEmpty(LibraryPath) || !HTTPManager.IOService.FileExists(LibraryPath))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.OpenRead))
|
||||
using (var br = new System.IO.BinaryReader(fs))
|
||||
{
|
||||
int version = br.ReadInt32();
|
||||
|
||||
int hostCount = br.ReadInt32();
|
||||
|
||||
for (int i = 0; i < hostCount; ++i)
|
||||
{
|
||||
GetHost(br.ReadString())
|
||||
.LoadFrom(version, br);
|
||||
}
|
||||
|
||||
HTTPManager.Logger.Information("HostManager", hostCount.ToString() + " HostDefinitions loaded!");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
HTTPManager.IOService.FileDelete(LibraryPath);
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
using System;
|
||||
using BestHTTP.Connections;
|
||||
using BestHTTP.Logger;
|
||||
|
||||
namespace BestHTTP.Core
|
||||
{
|
||||
public interface IHTTPRequestHandler : IDisposable
|
||||
{
|
||||
bool HasCustomRequestProcessor { get; }
|
||||
|
||||
KeepAliveHeader KeepAlive { get; }
|
||||
|
||||
bool CanProcessMultiple { get; }
|
||||
|
||||
ShutdownTypes ShutdownType { get; }
|
||||
|
||||
LoggingContext Context { get; }
|
||||
|
||||
void Process(HTTPRequest request);
|
||||
|
||||
void RunHandler();
|
||||
|
||||
/// <summary>
|
||||
/// An immediate shutdown request that called only on application closure.
|
||||
/// </summary>
|
||||
void Shutdown(ShutdownTypes type);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
|
||||
using BestHTTP.Logger;
|
||||
|
||||
namespace BestHTTP.Core
|
||||
{
|
||||
public struct HostConnectionKey
|
||||
{
|
||||
public readonly string Host;
|
||||
public readonly string Connection;
|
||||
|
||||
public HostConnectionKey(string host, string connection)
|
||||
{
|
||||
this.Host = host;
|
||||
this.Connection = connection;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HostConnectionKey Host: '{0}', Connection: '{1}']", this.Host, this.Connection);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IProtocol : IDisposable
|
||||
{
|
||||
HostConnectionKey ConnectionKey { get; }
|
||||
|
||||
bool IsClosed { get; }
|
||||
LoggingContext LoggingContext { get; }
|
||||
|
||||
void HandleEvents();
|
||||
|
||||
void CancellationRequested();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using BestHTTP.Logger;
|
||||
|
||||
// Required for ConcurrentQueue.Clear extension.
|
||||
using BestHTTP.Extensions;
|
||||
|
||||
namespace BestHTTP.Core
|
||||
{
|
||||
public enum PluginEvents
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_COOKIES
|
||||
SaveCookieLibrary,
|
||||
#endif
|
||||
|
||||
SaveCacheLibrary,
|
||||
|
||||
AltSvcHeader,
|
||||
|
||||
HTTP2ConnectProtocol
|
||||
}
|
||||
|
||||
public
|
||||
#if CSHARP_7_OR_LATER
|
||||
readonly
|
||||
#endif
|
||||
struct PluginEventInfo
|
||||
{
|
||||
public readonly PluginEvents Event;
|
||||
public readonly object Payload;
|
||||
|
||||
public PluginEventInfo(PluginEvents @event)
|
||||
{
|
||||
this.Event = @event;
|
||||
this.Payload = null;
|
||||
}
|
||||
|
||||
public PluginEventInfo(PluginEvents @event, object payload)
|
||||
{
|
||||
this.Event = @event;
|
||||
this.Payload = payload;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[PluginEventInfo Event: {0}]", this.Event);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PluginEventHelper
|
||||
{
|
||||
private static ConcurrentQueue<PluginEventInfo> pluginEvents = new ConcurrentQueue<PluginEventInfo>();
|
||||
|
||||
#pragma warning disable 0649
|
||||
public static Action<PluginEventInfo> OnEvent;
|
||||
#pragma warning restore
|
||||
|
||||
public static void EnqueuePluginEvent(PluginEventInfo @event)
|
||||
{
|
||||
if (HTTPManager.Logger.Level == Loglevels.All)
|
||||
HTTPManager.Logger.Information("PluginEventHelper", "Enqueue plugin event: " + @event.ToString());
|
||||
|
||||
pluginEvents.Enqueue(@event);
|
||||
}
|
||||
|
||||
internal static void Clear()
|
||||
{
|
||||
pluginEvents.Clear();
|
||||
}
|
||||
|
||||
internal static void ProcessQueue()
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_COOKIES
|
||||
bool saveCookieLibrary = false;
|
||||
#endif
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
bool saveCacheLibrary = false;
|
||||
#endif
|
||||
|
||||
PluginEventInfo pluginEvent;
|
||||
while (pluginEvents.TryDequeue(out pluginEvent))
|
||||
{
|
||||
if (HTTPManager.Logger.Level == Loglevels.All)
|
||||
HTTPManager.Logger.Information("PluginEventHelper", "Processing plugin event: " + pluginEvent.ToString());
|
||||
|
||||
if (OnEvent != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnEvent(pluginEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("PluginEventHelper", "ProcessQueue", ex);
|
||||
}
|
||||
}
|
||||
|
||||
switch (pluginEvent.Event)
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_COOKIES
|
||||
case PluginEvents.SaveCookieLibrary:
|
||||
saveCookieLibrary = true;
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
case PluginEvents.SaveCacheLibrary:
|
||||
saveCacheLibrary = true;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case PluginEvents.AltSvcHeader:
|
||||
AltSvcEventInfo altSvcEventInfo = pluginEvent.Payload as AltSvcEventInfo;
|
||||
HostManager.GetHost(altSvcEventInfo.Host)
|
||||
.HandleAltSvcHeader(altSvcEventInfo.Response);
|
||||
break;
|
||||
|
||||
case PluginEvents.HTTP2ConnectProtocol:
|
||||
HTTP2ConnectProtocolInfo info = pluginEvent.Payload as HTTP2ConnectProtocolInfo;
|
||||
HostManager.GetHost(info.Host)
|
||||
.HandleConnectProtocol(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if !BESTHTTP_DISABLE_COOKIES
|
||||
if (saveCookieLibrary)
|
||||
PlatformSupport.Threading.ThreadedRunner.RunShortLiving(Cookies.CookieJar.Persist);
|
||||
#endif
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
if (saveCacheLibrary)
|
||||
PlatformSupport.Threading.ThreadedRunner.RunShortLiving(Caching.HTTPCacheService.SaveLibrary);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AltSvcEventInfo
|
||||
{
|
||||
public readonly string Host;
|
||||
public readonly HTTPResponse Response;
|
||||
|
||||
public AltSvcEventInfo(string host, HTTPResponse resp)
|
||||
{
|
||||
this.Host = host;
|
||||
this.Response = resp;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class HTTP2ConnectProtocolInfo
|
||||
{
|
||||
public readonly string Host;
|
||||
public readonly bool Enabled;
|
||||
|
||||
public HTTP2ConnectProtocolInfo(string host, bool enabled)
|
||||
{
|
||||
this.Host = host;
|
||||
this.Enabled = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BestHTTP.Logger;
|
||||
|
||||
// Required for ConcurrentQueue.Clear extension.
|
||||
using BestHTTP.Extensions;
|
||||
|
||||
namespace BestHTTP.Core
|
||||
{
|
||||
public
|
||||
#if CSHARP_7_OR_LATER
|
||||
readonly
|
||||
#endif
|
||||
struct ProtocolEventInfo
|
||||
{
|
||||
public readonly IProtocol Source;
|
||||
|
||||
public ProtocolEventInfo(IProtocol source)
|
||||
{
|
||||
this.Source = source;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ProtocolEventInfo Source: {0}]", Source);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ProtocolEventHelper
|
||||
{
|
||||
private static ConcurrentQueue<ProtocolEventInfo> protocolEvents = new ConcurrentQueue<ProtocolEventInfo>();
|
||||
private static List<IProtocol> ActiveProtocols = new List<IProtocol>(2);
|
||||
|
||||
#pragma warning disable 0649
|
||||
public static Action<ProtocolEventInfo> OnEvent;
|
||||
#pragma warning restore
|
||||
|
||||
public static void EnqueueProtocolEvent(ProtocolEventInfo @event)
|
||||
{
|
||||
if (HTTPManager.Logger.Level == Loglevels.All)
|
||||
HTTPManager.Logger.Information("ProtocolEventHelper", "Enqueue protocol event: " + @event.ToString(), @event.Source.LoggingContext);
|
||||
|
||||
protocolEvents.Enqueue(@event);
|
||||
}
|
||||
|
||||
internal static void Clear()
|
||||
{
|
||||
protocolEvents.Clear();
|
||||
}
|
||||
|
||||
internal static void ProcessQueue()
|
||||
{
|
||||
ProtocolEventInfo protocolEvent;
|
||||
while (protocolEvents.TryDequeue(out protocolEvent))
|
||||
{
|
||||
if (HTTPManager.Logger.Level == Loglevels.All)
|
||||
HTTPManager.Logger.Information("ProtocolEventHelper", "Processing protocol event: " + protocolEvent.ToString(), protocolEvent.Source.LoggingContext);
|
||||
|
||||
if (OnEvent != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnEvent(protocolEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("ProtocolEventHelper", "ProcessQueue", ex, protocolEvent.Source.LoggingContext);
|
||||
}
|
||||
}
|
||||
|
||||
IProtocol protocol = protocolEvent.Source;
|
||||
|
||||
protocol.HandleEvents();
|
||||
|
||||
if (protocol.IsClosed)
|
||||
{
|
||||
ActiveProtocols.Remove(protocol);
|
||||
|
||||
HostManager.GetHost(protocol.ConnectionKey.Host)
|
||||
.GetHostDefinition(protocol.ConnectionKey.Connection)
|
||||
.TryToSendQueuedRequests();
|
||||
|
||||
protocol.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void AddProtocol(IProtocol protocol)
|
||||
{
|
||||
ActiveProtocols.Add(protocol);
|
||||
}
|
||||
|
||||
internal static void CancelActiveProtocols()
|
||||
{
|
||||
for (int i = 0; i < ActiveProtocols.Count; ++i)
|
||||
{
|
||||
var protocol = ActiveProtocols[i];
|
||||
|
||||
protocol.CancellationRequested();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,592 @@
|
|||
using BestHTTP.Extensions;
|
||||
using BestHTTP.Logger;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using BestHTTP.Timings;
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BestHTTP.Core
|
||||
{
|
||||
public enum RequestEvents
|
||||
{
|
||||
Upgraded,
|
||||
DownloadProgress,
|
||||
UploadProgress,
|
||||
StreamingData,
|
||||
StateChange,
|
||||
Resend,
|
||||
Headers,
|
||||
TimingData
|
||||
}
|
||||
|
||||
public
|
||||
#if CSHARP_7_OR_LATER
|
||||
readonly
|
||||
#endif
|
||||
struct RequestEventInfo
|
||||
{
|
||||
public readonly HTTPRequest SourceRequest;
|
||||
public readonly RequestEvents Event;
|
||||
|
||||
public readonly HTTPRequestStates State;
|
||||
|
||||
public readonly long Progress;
|
||||
public readonly long ProgressLength;
|
||||
|
||||
public readonly byte[] Data;
|
||||
public readonly int DataLength;
|
||||
|
||||
// Timing Data
|
||||
public readonly string Name;
|
||||
public readonly DateTime Time;
|
||||
public readonly TimeSpan Duration;
|
||||
|
||||
// Headers
|
||||
public readonly Dictionary<string, List<string>> Headers;
|
||||
|
||||
public RequestEventInfo(HTTPRequest request, RequestEvents @event)
|
||||
{
|
||||
this.SourceRequest = request;
|
||||
this.Event = @event;
|
||||
|
||||
this.State = HTTPRequestStates.Initial;
|
||||
|
||||
this.Progress = this.ProgressLength = 0;
|
||||
|
||||
this.Data = null;
|
||||
this.DataLength = 0;
|
||||
|
||||
// TimingData
|
||||
this.Name = null;
|
||||
this.Time = DateTime.MinValue;
|
||||
this.Duration = TimeSpan.Zero;
|
||||
|
||||
// Headers
|
||||
this.Headers = null;
|
||||
}
|
||||
|
||||
public RequestEventInfo(HTTPRequest request, HTTPRequestStates newState)
|
||||
{
|
||||
this.SourceRequest = request;
|
||||
this.Event = RequestEvents.StateChange;
|
||||
this.State = newState;
|
||||
|
||||
this.Progress = this.ProgressLength = 0;
|
||||
this.Data = null;
|
||||
this.DataLength = 0;
|
||||
|
||||
// TimingData
|
||||
this.Name = null;
|
||||
this.Time = DateTime.MinValue;
|
||||
this.Duration = TimeSpan.Zero;
|
||||
|
||||
// Headers
|
||||
this.Headers = null;
|
||||
}
|
||||
|
||||
public RequestEventInfo(HTTPRequest request, RequestEvents @event, long progress, long progressLength)
|
||||
{
|
||||
this.SourceRequest = request;
|
||||
this.Event = @event;
|
||||
this.State = HTTPRequestStates.Initial;
|
||||
|
||||
this.Progress = progress;
|
||||
this.ProgressLength = progressLength;
|
||||
this.Data = null;
|
||||
this.DataLength = 0;
|
||||
|
||||
// TimingData
|
||||
this.Name = null;
|
||||
this.Time = DateTime.MinValue;
|
||||
this.Duration = TimeSpan.Zero;
|
||||
|
||||
// Headers
|
||||
this.Headers = null;
|
||||
}
|
||||
|
||||
public RequestEventInfo(HTTPRequest request, byte[] data, int dataLength)
|
||||
{
|
||||
this.SourceRequest = request;
|
||||
this.Event = RequestEvents.StreamingData;
|
||||
this.State = HTTPRequestStates.Initial;
|
||||
|
||||
this.Progress = this.ProgressLength = 0;
|
||||
this.Data = data;
|
||||
this.DataLength = dataLength;
|
||||
|
||||
// TimingData
|
||||
this.Name = null;
|
||||
this.Time = DateTime.MinValue;
|
||||
this.Duration = TimeSpan.Zero;
|
||||
|
||||
// Headers
|
||||
this.Headers = null;
|
||||
}
|
||||
|
||||
public RequestEventInfo(HTTPRequest request, string name, DateTime time)
|
||||
{
|
||||
this.SourceRequest = request;
|
||||
this.Event = RequestEvents.TimingData;
|
||||
this.State = HTTPRequestStates.Initial;
|
||||
|
||||
this.Progress = this.ProgressLength = 0;
|
||||
this.Data = null;
|
||||
this.DataLength = 0;
|
||||
|
||||
// TimingData
|
||||
this.Name = name;
|
||||
this.Time = time;
|
||||
this.Duration = TimeSpan.Zero;
|
||||
|
||||
// Headers
|
||||
this.Headers = null;
|
||||
}
|
||||
|
||||
public RequestEventInfo(HTTPRequest request, string name, TimeSpan duration)
|
||||
{
|
||||
this.SourceRequest = request;
|
||||
this.Event = RequestEvents.TimingData;
|
||||
this.State = HTTPRequestStates.Initial;
|
||||
|
||||
this.Progress = this.ProgressLength = 0;
|
||||
this.Data = null;
|
||||
this.DataLength = 0;
|
||||
|
||||
// TimingData
|
||||
this.Name = name;
|
||||
this.Time = DateTime.Now;
|
||||
this.Duration = duration;
|
||||
|
||||
// Headers
|
||||
this.Headers = null;
|
||||
}
|
||||
|
||||
public RequestEventInfo(HTTPRequest request, Dictionary<string, List<string>> headers)
|
||||
{
|
||||
this.SourceRequest = request;
|
||||
this.Event = RequestEvents.Headers;
|
||||
this.State = HTTPRequestStates.Initial;
|
||||
|
||||
this.Progress = this.ProgressLength = 0;
|
||||
this.Data = null;
|
||||
this.DataLength = 0;
|
||||
|
||||
// TimingData
|
||||
this.Name = null;
|
||||
this.Time = DateTime.MinValue;
|
||||
this.Duration = TimeSpan.Zero;
|
||||
|
||||
// Headers
|
||||
this.Headers = headers;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
switch (this.Event)
|
||||
{
|
||||
case RequestEvents.Upgraded:
|
||||
return string.Format("[RequestEventInfo SourceRequest: {0}, Event: Upgraded]", this.SourceRequest.CurrentUri);
|
||||
case RequestEvents.DownloadProgress:
|
||||
return string.Format("[RequestEventInfo SourceRequest: {0}, Event: DownloadProgress, Progress: {1}, ProgressLength: {2}]", this.SourceRequest.CurrentUri, this.Progress, this.ProgressLength);
|
||||
case RequestEvents.UploadProgress:
|
||||
return string.Format("[RequestEventInfo SourceRequest: {0}, Event: UploadProgress, Progress: {1}, ProgressLength: {2}]", this.SourceRequest.CurrentUri, this.Progress, this.ProgressLength);
|
||||
case RequestEvents.StreamingData:
|
||||
return string.Format("[RequestEventInfo SourceRequest: {0}, Event: StreamingData, DataLength: {1}]", this.SourceRequest.CurrentUri, this.DataLength);
|
||||
case RequestEvents.StateChange:
|
||||
return string.Format("[RequestEventInfo SourceRequest: {0}, Event: StateChange, State: {1}]", this.SourceRequest.CurrentUri, this.State);
|
||||
case RequestEvents.Resend:
|
||||
return string.Format("[RequestEventInfo SourceRequest: {0}, Event: Resend]", this.SourceRequest.CurrentUri);
|
||||
case RequestEvents.Headers:
|
||||
return string.Format("[RequestEventInfo SourceRequest: {0}, Event: Headers]", this.SourceRequest.CurrentUri);
|
||||
case RequestEvents.TimingData:
|
||||
if (this.Duration == TimeSpan.Zero)
|
||||
return string.Format("[RequestEventInfo SourceRequest: {0}, Event: TimingData, Name: {1}, Time: {2}]", this.SourceRequest.CurrentUri, this.Name, this.Time);
|
||||
else
|
||||
return string.Format("[RequestEventInfo SourceRequest: {0}, Event: TimingData, Name: {1}, Time: {2}, Duration: {3}]", this.SourceRequest.CurrentUri, this.Name, this.Time, this.Duration);
|
||||
default:
|
||||
throw new NotImplementedException(this.Event.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ProgressFlattener
|
||||
{
|
||||
struct FlattenedProgress
|
||||
{
|
||||
public HTTPRequest request;
|
||||
public OnProgressDelegate onProgress;
|
||||
public long progress;
|
||||
public long length;
|
||||
}
|
||||
|
||||
private FlattenedProgress[] progresses;
|
||||
private bool hasProgress;
|
||||
|
||||
public void InsertOrUpdate(RequestEventInfo info, OnProgressDelegate onProgress)
|
||||
{
|
||||
if (progresses == null)
|
||||
progresses = new FlattenedProgress[1];
|
||||
|
||||
hasProgress = true;
|
||||
|
||||
var newProgresss = new FlattenedProgress { request = info.SourceRequest, progress = info.Progress, length = info.ProgressLength, onProgress = onProgress };
|
||||
|
||||
int firstEmptyIdx = -1;
|
||||
for (int i = 0; i < progresses.Length; i++)
|
||||
{
|
||||
var progress = progresses[i];
|
||||
if (object.ReferenceEquals(progress.request, info.SourceRequest))
|
||||
{
|
||||
progresses[i] = newProgresss;
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstEmptyIdx == -1 && progress.request == null)
|
||||
firstEmptyIdx = i;
|
||||
}
|
||||
|
||||
if (firstEmptyIdx == -1)
|
||||
{
|
||||
Array.Resize(ref progresses, progresses.Length + 1);
|
||||
progresses[progresses.Length - 1] = newProgresss;
|
||||
}
|
||||
else
|
||||
progresses[firstEmptyIdx] = newProgresss;
|
||||
}
|
||||
|
||||
public void DispatchProgressCallbacks()
|
||||
{
|
||||
if (progresses == null || !hasProgress)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < progresses.Length; ++i)
|
||||
{
|
||||
var @event = progresses[i];
|
||||
var source = @event.request;
|
||||
if (source != null && @event.onProgress != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@event.onProgress(source, @event.progress, @event.length);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("ProgressFlattener", "DispatchProgressCallbacks", ex, source.Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Array.Clear(progresses, 0, progresses.Length);
|
||||
hasProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestEventHelper
|
||||
{
|
||||
private static ConcurrentQueue<RequestEventInfo> requestEventQueue = new ConcurrentQueue<RequestEventInfo>();
|
||||
|
||||
#pragma warning disable 0649
|
||||
public static Action<RequestEventInfo> OnEvent;
|
||||
#pragma warning restore
|
||||
|
||||
// Low frame rate and hight download/upload speed can add more download/upload progress events to dispatch in one frame.
|
||||
// This can add higher CPU usage as it might cause updating the UI/do other things unnecessary in the same frame.
|
||||
// To avoid this, instead of calling the events directly, we store the last event's data and call download/upload callbacks only once per frame.
|
||||
|
||||
private static ProgressFlattener downloadProgress;
|
||||
private static ProgressFlattener uploadProgress;
|
||||
|
||||
public static void EnqueueRequestEvent(RequestEventInfo @event)
|
||||
{
|
||||
if (HTTPManager.Logger.Level == Loglevels.All)
|
||||
HTTPManager.Logger.Information("RequestEventHelper", "Enqueue request event: " + @event.ToString(), @event.SourceRequest.Context);
|
||||
|
||||
requestEventQueue.Enqueue(@event);
|
||||
}
|
||||
|
||||
internal static void Clear()
|
||||
{
|
||||
requestEventQueue.Clear();
|
||||
}
|
||||
|
||||
internal static void ProcessQueue()
|
||||
{
|
||||
RequestEventInfo requestEvent;
|
||||
while (requestEventQueue.TryDequeue(out requestEvent))
|
||||
{
|
||||
HTTPRequest source = requestEvent.SourceRequest;
|
||||
|
||||
if (HTTPManager.Logger.Level == Loglevels.All)
|
||||
HTTPManager.Logger.Information("RequestEventHelper", "Processing request event: " + requestEvent.ToString(), source.Context);
|
||||
|
||||
if (OnEvent != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnEvent(requestEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("RequestEventHelper", "ProcessQueue", ex, source.Context);
|
||||
}
|
||||
}
|
||||
|
||||
switch (requestEvent.Event)
|
||||
{
|
||||
case RequestEvents.StreamingData:
|
||||
{
|
||||
var response = source.Response;
|
||||
if (response != null)
|
||||
System.Threading.Interlocked.Decrement(ref response.UnprocessedFragments);
|
||||
|
||||
bool reuseBuffer = true;
|
||||
try
|
||||
{
|
||||
if (source.UseStreaming)
|
||||
reuseBuffer = source.OnStreamingData(source, response, requestEvent.Data, requestEvent.DataLength);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.StreamingData", ex, source.Context);
|
||||
}
|
||||
|
||||
if (reuseBuffer)
|
||||
BufferPool.Release(requestEvent.Data);
|
||||
break;
|
||||
}
|
||||
|
||||
case RequestEvents.DownloadProgress:
|
||||
try
|
||||
{
|
||||
if (source.OnDownloadProgress != null)
|
||||
{
|
||||
if (downloadProgress == null)
|
||||
downloadProgress = new ProgressFlattener();
|
||||
downloadProgress.InsertOrUpdate(requestEvent, source.OnDownloadProgress);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.DownloadProgress", ex, source.Context);
|
||||
}
|
||||
break;
|
||||
|
||||
case RequestEvents.UploadProgress:
|
||||
try
|
||||
{
|
||||
if (source.OnUploadProgress != null)
|
||||
{
|
||||
if (uploadProgress == null)
|
||||
uploadProgress = new ProgressFlattener();
|
||||
uploadProgress.InsertOrUpdate(requestEvent, source.OnUploadProgress);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.UploadProgress", ex, source.Context);
|
||||
}
|
||||
break;
|
||||
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
case RequestEvents.Upgraded:
|
||||
try
|
||||
{
|
||||
if (source.OnUpgraded != null)
|
||||
source.OnUpgraded(source, source.Response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.Upgraded", ex, source.Context);
|
||||
}
|
||||
|
||||
IProtocol protocol = source.Response as IProtocol;
|
||||
if (protocol != null)
|
||||
ProtocolEventHelper.AddProtocol(protocol);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case RequestEvents.Resend:
|
||||
source.State = HTTPRequestStates.Initial;
|
||||
|
||||
var host = HostManager.GetHost(source.CurrentUri.Host);
|
||||
|
||||
host.Send(source);
|
||||
|
||||
break;
|
||||
|
||||
case RequestEvents.Headers:
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = source.Response;
|
||||
if (source.OnHeadersReceived != null && response != null)
|
||||
source.OnHeadersReceived(source, response, requestEvent.Headers);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.Headers", ex, source.Context);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RequestEvents.StateChange:
|
||||
try
|
||||
{
|
||||
RequestEventHelper.HandleRequestStateChange(requestEvent);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("RequestEventHelper", "HandleRequestStateChange", ex, source.Context);
|
||||
}
|
||||
break;
|
||||
|
||||
case RequestEvents.TimingData:
|
||||
source.Timing.AddEvent(requestEvent.Name, requestEvent.Time, requestEvent.Duration);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uploadProgress?.DispatchProgressCallbacks();
|
||||
downloadProgress?.DispatchProgressCallbacks();
|
||||
}
|
||||
|
||||
private static bool AbortRequestWhenTimedOut(DateTime now, object context)
|
||||
{
|
||||
HTTPRequest request = context as HTTPRequest;
|
||||
|
||||
if (request.State >= HTTPRequestStates.Finished)
|
||||
return false; // don't repeat
|
||||
|
||||
// Protocols will shut down themselves
|
||||
if (request.Response is IProtocol)
|
||||
return false;
|
||||
|
||||
if (request.IsTimedOut)
|
||||
{
|
||||
HTTPManager.Logger.Information("RequestEventHelper", "AbortRequestWhenTimedOut - Request timed out. CurrentUri: " + request.CurrentUri.ToString(), request.Context);
|
||||
request.Abort();
|
||||
|
||||
return false; // don't repeat
|
||||
}
|
||||
|
||||
return true; // repeat
|
||||
}
|
||||
|
||||
internal static void HandleRequestStateChange(RequestEventInfo @event)
|
||||
{
|
||||
HTTPRequest source = @event.SourceRequest;
|
||||
|
||||
// Because there's a race condition between setting the request's State in its Abort() function running on Unity's main thread
|
||||
// and the HTTP1/HTTP2 handlers running on an another one.
|
||||
// Because of these race conditions cases violating expectations can be:
|
||||
// 1.) State is finished but the response null
|
||||
// 2.) State is (Connection)TimedOut and the response non-null
|
||||
// We have to make sure that no callbacks are called twice and in the request must be in a consistent state!
|
||||
|
||||
// State | Request
|
||||
// --------- +---------
|
||||
// 1 Null
|
||||
// Finished | Skip
|
||||
// Timeout/Abort | Deliver
|
||||
//
|
||||
// 2 Non-Null
|
||||
// Finished | Deliver
|
||||
// Timeout/Abort | Skip
|
||||
|
||||
switch (@event.State)
|
||||
{
|
||||
case HTTPRequestStates.Queued:
|
||||
source.QueuedAt = DateTime.UtcNow;
|
||||
if ((!source.UseStreaming && source.UploadStream == null) || source.EnableTimoutForStreaming)
|
||||
BestHTTP.Extensions.Timer.Add(new TimerData(TimeSpan.FromSeconds(1), @event.SourceRequest, AbortRequestWhenTimedOut));
|
||||
break;
|
||||
|
||||
case HTTPRequestStates.ConnectionTimedOut:
|
||||
case HTTPRequestStates.TimedOut:
|
||||
case HTTPRequestStates.Error:
|
||||
case HTTPRequestStates.Aborted:
|
||||
source.Response = null;
|
||||
goto case HTTPRequestStates.Finished;
|
||||
|
||||
case HTTPRequestStates.Finished:
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING
|
||||
// Here we will try to load content for a failed load. Failed load is a request with ConnectionTimedOut, TimedOut or Error state.
|
||||
// A request with Finished state but response with status code >= 500 also something that we will try to load from the cache.
|
||||
// We have to set what we going to try to load here too (other place is inside IsCachedEntityExpiresInTheFuture) as we don't want to load a cached content for
|
||||
// a request that just finished without any problem!
|
||||
|
||||
try
|
||||
{
|
||||
bool tryLoad = !source.DisableCache && source.State != HTTPRequestStates.Aborted && (source.State != HTTPRequestStates.Finished || source.Response == null || source.Response.StatusCode >= 500);
|
||||
if (tryLoad && Caching.HTTPCacheService.IsCachedEntityExpiresInTheFuture(source))
|
||||
{
|
||||
HTTPManager.Logger.Information("RequestEventHelper", "IsCachedEntityExpiresInTheFuture check returned true! CurrentUri: " + source.CurrentUri.ToString(), source.Context);
|
||||
|
||||
PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTPRequest>((req) =>
|
||||
{
|
||||
// Disable any other cache activity.
|
||||
req.DisableCache = true;
|
||||
|
||||
var originalState = req.State;
|
||||
if (Connections.ConnectionHelper.TryLoadAllFromCache("RequestEventHelper", req, req.Context))
|
||||
{
|
||||
if (req.State != HTTPRequestStates.Finished)
|
||||
req.State = HTTPRequestStates.Finished;
|
||||
else
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(req, HTTPRequestStates.Finished));
|
||||
}
|
||||
else
|
||||
{
|
||||
HTTPManager.Logger.Information("RequestEventHelper", "TryLoadAllFromCache failed to load! CurrentUri: " + req.CurrentUri.ToString(), source.Context);
|
||||
|
||||
// If for some reason it couldn't load we place back the request to the queue.
|
||||
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(req, originalState));
|
||||
}
|
||||
}, source);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("RequestEventHelper", string.Format("HandleRequestStateChange - Cache probe - CurrentUri: \"{0}\" State: {1} StatusCode: {2}", source.CurrentUri, source.State, source.Response != null ? source.Response.StatusCode : 0), ex, source.Context);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Dispatch any collected download/upload progress, otherwise they would _after_ the callback!
|
||||
uploadProgress?.DispatchProgressCallbacks();
|
||||
downloadProgress?.DispatchProgressCallbacks();
|
||||
|
||||
source.Timing.AddEvent(TimingEventNames.Queued_For_Disptach, DateTime.Now, TimeSpan.Zero);
|
||||
source.Timing.AddEvent(TimingEventNames.Finished, DateTime.Now, DateTime.Now - source.Timing.Start);
|
||||
|
||||
if (source.Callback != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
source.Callback(source, source.Response);
|
||||
|
||||
source.Timing.AddEvent(TimingEventNames.Callback, DateTime.Now, TimeSpan.Zero);
|
||||
|
||||
if (HTTPManager.Logger.Level <= Loglevels.Information)
|
||||
HTTPManager.Logger.Information("RequestEventHelper", "Finishing request. Timings: " + source.Timing.ToString(), source.Context);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("RequestEventHelper", "HandleRequestStateChange " + @event.State, ex, source.Context);
|
||||
}
|
||||
}
|
||||
|
||||
source.Dispose();
|
||||
|
||||
HostManager.GetHost(source.CurrentUri.Host)
|
||||
.GetHostDefinition(HostDefinition.GetKeyForRequest(source))
|
||||
.TryToSendQueuedRequests();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
|
||||
namespace BestHTTP.Decompression
|
||||
{
|
||||
public sealed class BrotliDecompressor : IDecompressor
|
||||
{
|
||||
#if ((NET_STANDARD_2_1 || UNITY_2021_2_OR_NEWER) && (!(ENABLE_MONO && UNITY_ANDROID) || (!UNITY_WEBGL || UNITY_EDITOR))) && BESTHTTP_ENABLE_BROTLI
|
||||
private BufferPoolMemoryStream decompressorInputStream;
|
||||
private BufferPoolMemoryStream decompressorOutputStream;
|
||||
private System.IO.Compression.BrotliStream decompressorStream;
|
||||
#endif
|
||||
|
||||
private int MinLengthToDecompress = 256;
|
||||
|
||||
public static bool IsSupported()
|
||||
{
|
||||
// Not enabled under android with the mono runtime
|
||||
#if ((NET_STANDARD_2_1 || UNITY_2021_2_OR_NEWER) && (!(ENABLE_MONO && UNITY_ANDROID) || (!UNITY_WEBGL || UNITY_EDITOR))) && BESTHTTP_ENABLE_BROTLI
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public BrotliDecompressor(int minLengthToDecompress)
|
||||
{
|
||||
this.MinLengthToDecompress = minLengthToDecompress;
|
||||
}
|
||||
|
||||
public DecompressedData Decompress(byte[] data, int offset, int count, bool forceDecompress = false, bool dataCanBeLarger = false)
|
||||
{
|
||||
#if ((NET_STANDARD_2_1 || UNITY_2021_2_OR_NEWER) && (!(ENABLE_MONO && UNITY_ANDROID) || (!UNITY_WEBGL || UNITY_EDITOR))) && BESTHTTP_ENABLE_BROTLI
|
||||
if (decompressorInputStream == null)
|
||||
decompressorInputStream = new BufferPoolMemoryStream(count);
|
||||
|
||||
if (data != null)
|
||||
decompressorInputStream.Write(data, offset, count);
|
||||
|
||||
if (!forceDecompress && decompressorInputStream.Length < MinLengthToDecompress)
|
||||
return new DecompressedData(null, 0);
|
||||
|
||||
decompressorInputStream.Position = 0;
|
||||
|
||||
if (decompressorStream == null)
|
||||
{
|
||||
decompressorStream = new System.IO.Compression.BrotliStream(decompressorInputStream,
|
||||
System.IO.Compression.CompressionMode.Decompress,
|
||||
true);
|
||||
}
|
||||
|
||||
if (decompressorOutputStream == null)
|
||||
decompressorOutputStream = new BufferPoolMemoryStream();
|
||||
decompressorOutputStream.SetLength(0);
|
||||
|
||||
byte[] copyBuffer = BufferPool.Get(1024, true);
|
||||
|
||||
int readCount;
|
||||
int sumReadCount = 0;
|
||||
while ((readCount = decompressorStream.Read(copyBuffer, 0, copyBuffer.Length)) != 0)
|
||||
{
|
||||
decompressorOutputStream.Write(copyBuffer, 0, readCount);
|
||||
sumReadCount += readCount;
|
||||
}
|
||||
|
||||
BufferPool.Release(copyBuffer);
|
||||
|
||||
// If no read is done (returned with any data) don't zero out the input stream, as it would delete any not yet used data.
|
||||
if (sumReadCount > 0)
|
||||
decompressorStream.SetLength(0);
|
||||
|
||||
byte[] result = decompressorOutputStream.ToArray(dataCanBeLarger);
|
||||
|
||||
return new DecompressedData(result, dataCanBeLarger ? (int)decompressorOutputStream.Length : result.Length);
|
||||
#else
|
||||
return default(DecompressedData);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
#if ((NET_STANDARD_2_1 || UNITY_2021_2_OR_NEWER) && (!(ENABLE_MONO && UNITY_ANDROID) || (!UNITY_WEBGL || UNITY_EDITOR))) && BESTHTTP_ENABLE_BROTLI
|
||||
this.decompressorStream?.Dispose();
|
||||
this.decompressorStream = null;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,473 @@
|
|||
// CRC32.cs
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2011 Dino Chiesa.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This code module is part of DotNetZip, a zipfile class library.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This code is licensed under the Microsoft Public License.
|
||||
// See the file License.txt for the license details.
|
||||
// More info on: http://dotnetzip.codeplex.com
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Last Saved: <2011-August-02 18:25:54>
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This module defines the CRC32 class, which can do the CRC32 algorithm, using
|
||||
// arbitrary starting polynomials, and bit reversal. The bit reversal is what
|
||||
// distinguishes this CRC-32 used in BZip2 from the CRC-32 that is used in PKZIP
|
||||
// files, or GZIP files. This class does both.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
|
||||
using System;
|
||||
using Interop = System.Runtime.InteropServices;
|
||||
|
||||
namespace BestHTTP.Decompression.Crc
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes a CRC-32. The CRC-32 algorithm is parameterized - you
|
||||
/// can set the polynomial and enable or disable bit
|
||||
/// reversal. This can be used for GZIP, BZip2, or ZIP.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This type is used internally by DotNetZip; it is generally not used
|
||||
/// directly by applications wishing to create, read, or manipulate zip
|
||||
/// archive files.
|
||||
/// </remarks>
|
||||
|
||||
internal class CRC32
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the total number of bytes applied to the CRC.
|
||||
/// </summary>
|
||||
public Int64 TotalBytesRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return _TotalBytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the current CRC for all blocks slurped in.
|
||||
/// </summary>
|
||||
public Int32 Crc32Result
|
||||
{
|
||||
get
|
||||
{
|
||||
return unchecked((Int32)(~_register));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the CRC32 for the specified stream.
|
||||
/// </summary>
|
||||
/// <param name="input">The stream over which to calculate the CRC32</param>
|
||||
/// <returns>the CRC32 calculation</returns>
|
||||
public Int32 GetCrc32(System.IO.Stream input)
|
||||
{
|
||||
return GetCrc32AndCopy(input, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the CRC32 for the specified stream, and writes the input into the
|
||||
/// output stream.
|
||||
/// </summary>
|
||||
/// <param name="input">The stream over which to calculate the CRC32</param>
|
||||
/// <param name="output">The stream into which to deflate the input</param>
|
||||
/// <returns>the CRC32 calculation</returns>
|
||||
public Int32 GetCrc32AndCopy(System.IO.Stream input, System.IO.Stream output)
|
||||
{
|
||||
if (input == null)
|
||||
throw new Exception("The input stream must not be null.");
|
||||
|
||||
unchecked
|
||||
{
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int readSize = BUFFER_SIZE;
|
||||
|
||||
_TotalBytesRead = 0;
|
||||
int count = input.Read(buffer, 0, readSize);
|
||||
if (output != null) output.Write(buffer, 0, count);
|
||||
_TotalBytesRead += count;
|
||||
while (count > 0)
|
||||
{
|
||||
SlurpBlock(buffer, 0, count);
|
||||
count = input.Read(buffer, 0, readSize);
|
||||
if (output != null) output.Write(buffer, 0, count);
|
||||
_TotalBytesRead += count;
|
||||
}
|
||||
|
||||
return (Int32)(~_register);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the CRC32 for the given (word,byte) combo. This is a
|
||||
/// computation defined by PKzip for PKZIP 2.0 (weak) encryption.
|
||||
/// </summary>
|
||||
/// <param name="W">The word to start with.</param>
|
||||
/// <param name="B">The byte to combine it with.</param>
|
||||
/// <returns>The CRC-ized result.</returns>
|
||||
public Int32 ComputeCrc32(Int32 W, byte B)
|
||||
{
|
||||
return _InternalComputeCrc32((UInt32)W, B);
|
||||
}
|
||||
|
||||
internal Int32 _InternalComputeCrc32(UInt32 W, byte B)
|
||||
{
|
||||
return (Int32)(crc32Table[(W ^ B) & 0xFF] ^ (W >> 8));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update the value for the running CRC32 using the given block of bytes.
|
||||
/// This is useful when using the CRC32() class in a Stream.
|
||||
/// </summary>
|
||||
/// <param name="block">block of bytes to slurp</param>
|
||||
/// <param name="offset">starting point in the block</param>
|
||||
/// <param name="count">how many bytes within the block to slurp</param>
|
||||
public void SlurpBlock(byte[] block, int offset, int count)
|
||||
{
|
||||
if (block == null)
|
||||
throw new Exception("The data buffer must not be null.");
|
||||
|
||||
// bzip algorithm
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int x = offset + i;
|
||||
byte b = block[x];
|
||||
if (this.reverseBits)
|
||||
{
|
||||
UInt32 temp = (_register >> 24) ^ b;
|
||||
_register = (_register << 8) ^ crc32Table[temp];
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt32 temp = (_register & 0x000000FF) ^ b;
|
||||
_register = (_register >> 8) ^ crc32Table[temp];
|
||||
}
|
||||
}
|
||||
_TotalBytesRead += count;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Process one byte in the CRC.
|
||||
/// </summary>
|
||||
/// <param name = "b">the byte to include into the CRC . </param>
|
||||
public void UpdateCRC(byte b)
|
||||
{
|
||||
if (this.reverseBits)
|
||||
{
|
||||
UInt32 temp = (_register >> 24) ^ b;
|
||||
_register = (_register << 8) ^ crc32Table[temp];
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt32 temp = (_register & 0x000000FF) ^ b;
|
||||
_register = (_register >> 8) ^ crc32Table[temp];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a run of N identical bytes into the CRC.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method serves as an optimization for updating the CRC when a
|
||||
/// run of identical bytes is found. Rather than passing in a buffer of
|
||||
/// length n, containing all identical bytes b, this method accepts the
|
||||
/// byte value and the length of the (virtual) buffer - the length of
|
||||
/// the run.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name = "b">the byte to include into the CRC. </param>
|
||||
/// <param name = "n">the number of times that byte should be repeated. </param>
|
||||
public void UpdateCRC(byte b, int n)
|
||||
{
|
||||
while (n-- > 0)
|
||||
{
|
||||
if (this.reverseBits)
|
||||
{
|
||||
uint temp = (_register >> 24) ^ b;
|
||||
_register = (_register << 8) ^ crc32Table[(temp >= 0)
|
||||
? temp
|
||||
: (temp + 256)];
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt32 temp = (_register & 0x000000FF) ^ b;
|
||||
_register = (_register >> 8) ^ crc32Table[(temp >= 0)
|
||||
? temp
|
||||
: (temp + 256)];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static uint ReverseBits(uint data)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
uint ret = data;
|
||||
ret = (ret & 0x55555555) << 1 | (ret >> 1) & 0x55555555;
|
||||
ret = (ret & 0x33333333) << 2 | (ret >> 2) & 0x33333333;
|
||||
ret = (ret & 0x0F0F0F0F) << 4 | (ret >> 4) & 0x0F0F0F0F;
|
||||
ret = (ret << 24) | ((ret & 0xFF00) << 8) | ((ret >> 8) & 0xFF00) | (ret >> 24);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte ReverseBits(byte data)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
uint u = (uint)data * 0x00020202;
|
||||
uint m = 0x01044010;
|
||||
uint s = u & m;
|
||||
uint t = (u << 2) & (m << 1);
|
||||
return (byte)((0x01001001 * (s + t)) >> 24);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void GenerateLookupTable()
|
||||
{
|
||||
crc32Table = new UInt32[256];
|
||||
unchecked
|
||||
{
|
||||
UInt32 dwCrc;
|
||||
byte i = 0;
|
||||
do
|
||||
{
|
||||
dwCrc = i;
|
||||
for (byte j = 8; j > 0; j--)
|
||||
{
|
||||
if ((dwCrc & 1) == 1)
|
||||
{
|
||||
dwCrc = (dwCrc >> 1) ^ dwPolynomial;
|
||||
}
|
||||
else
|
||||
{
|
||||
dwCrc >>= 1;
|
||||
}
|
||||
}
|
||||
if (reverseBits)
|
||||
{
|
||||
crc32Table[ReverseBits(i)] = ReverseBits(dwCrc);
|
||||
}
|
||||
else
|
||||
{
|
||||
crc32Table[i] = dwCrc;
|
||||
}
|
||||
i++;
|
||||
} while (i!=0);
|
||||
}
|
||||
|
||||
#if VERBOSE
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("private static readonly UInt32[] crc32Table = {");
|
||||
for (int i = 0; i < crc32Table.Length; i+=4)
|
||||
{
|
||||
Console.Write(" ");
|
||||
for (int j=0; j < 4; j++)
|
||||
{
|
||||
Console.Write(" 0x{0:X8}U,", crc32Table[i+j]);
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
Console.WriteLine("};");
|
||||
Console.WriteLine();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
private uint gf2_matrix_times(uint[] matrix, uint vec)
|
||||
{
|
||||
uint sum = 0;
|
||||
int i=0;
|
||||
while (vec != 0)
|
||||
{
|
||||
if ((vec & 0x01)== 0x01)
|
||||
sum ^= matrix[i];
|
||||
vec >>= 1;
|
||||
i++;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
private void gf2_matrix_square(uint[] square, uint[] mat)
|
||||
{
|
||||
for (int i = 0; i < 32; i++)
|
||||
square[i] = gf2_matrix_times(mat, mat[i]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Combines the given CRC32 value with the current running total.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is useful when using a divide-and-conquer approach to
|
||||
/// calculating a CRC. Multiple threads can each calculate a
|
||||
/// CRC32 on a segment of the data, and then combine the
|
||||
/// individual CRC32 values at the end.
|
||||
/// </remarks>
|
||||
/// <param name="crc">the crc value to be combined with this one</param>
|
||||
/// <param name="length">the length of data the CRC value was calculated on</param>
|
||||
public void Combine(int crc, int length)
|
||||
{
|
||||
uint[] even = new uint[32]; // even-power-of-two zeros operator
|
||||
uint[] odd = new uint[32]; // odd-power-of-two zeros operator
|
||||
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
uint crc1= ~_register;
|
||||
uint crc2= (uint) crc;
|
||||
|
||||
// put operator for one zero bit in odd
|
||||
odd[0] = this.dwPolynomial; // the CRC-32 polynomial
|
||||
uint row = 1;
|
||||
for (int i = 1; i < 32; i++)
|
||||
{
|
||||
odd[i] = row;
|
||||
row <<= 1;
|
||||
}
|
||||
|
||||
// put operator for two zero bits in even
|
||||
gf2_matrix_square(even, odd);
|
||||
|
||||
// put operator for four zero bits in odd
|
||||
gf2_matrix_square(odd, even);
|
||||
|
||||
uint len2 = (uint) length;
|
||||
|
||||
// apply len2 zeros to crc1 (first square will put the operator for one
|
||||
// zero byte, eight zero bits, in even)
|
||||
do {
|
||||
// apply zeros operator for this bit of len2
|
||||
gf2_matrix_square(even, odd);
|
||||
|
||||
if ((len2 & 1)== 1)
|
||||
crc1 = gf2_matrix_times(even, crc1);
|
||||
len2 >>= 1;
|
||||
|
||||
if (len2 == 0)
|
||||
break;
|
||||
|
||||
// another iteration of the loop with odd and even swapped
|
||||
gf2_matrix_square(odd, even);
|
||||
if ((len2 & 1)==1)
|
||||
crc1 = gf2_matrix_times(odd, crc1);
|
||||
len2 >>= 1;
|
||||
|
||||
|
||||
} while (len2 != 0);
|
||||
|
||||
crc1 ^= crc2;
|
||||
|
||||
_register= ~crc1;
|
||||
|
||||
//return (int) crc1;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of the CRC32 class using the default settings: no
|
||||
/// bit reversal, and a polynomial of 0xEDB88320.
|
||||
/// </summary>
|
||||
public CRC32() : this(false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of the CRC32 class, specifying whether to reverse
|
||||
/// data bits or not.
|
||||
/// </summary>
|
||||
/// <param name='reverseBits'>
|
||||
/// specify true if the instance should reverse data bits.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// In the CRC-32 used by BZip2, the bits are reversed. Therefore if you
|
||||
/// want a CRC32 with compatibility with BZip2, you should pass true
|
||||
/// here. In the CRC-32 used by GZIP and PKZIP, the bits are not
|
||||
/// reversed; Therefore if you want a CRC32 with compatibility with
|
||||
/// those, you should pass false.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public CRC32(bool reverseBits) :
|
||||
this( unchecked((int)0xEDB88320), reverseBits)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of the CRC32 class, specifying the polynomial and
|
||||
/// whether to reverse data bits or not.
|
||||
/// </summary>
|
||||
/// <param name='polynomial'>
|
||||
/// The polynomial to use for the CRC, expressed in the reversed (LSB)
|
||||
/// format: the highest ordered bit in the polynomial value is the
|
||||
/// coefficient of the 0th power; the second-highest order bit is the
|
||||
/// coefficient of the 1 power, and so on. Expressed this way, the
|
||||
/// polynomial for the CRC-32C used in IEEE 802.3, is 0xEDB88320.
|
||||
/// </param>
|
||||
/// <param name='reverseBits'>
|
||||
/// specify true if the instance should reverse data bits.
|
||||
/// </param>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// In the CRC-32 used by BZip2, the bits are reversed. Therefore if you
|
||||
/// want a CRC32 with compatibility with BZip2, you should pass true
|
||||
/// here for the <c>reverseBits</c> parameter. In the CRC-32 used by
|
||||
/// GZIP and PKZIP, the bits are not reversed; Therefore if you want a
|
||||
/// CRC32 with compatibility with those, you should pass false for the
|
||||
/// <c>reverseBits</c> parameter.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public CRC32(int polynomial, bool reverseBits)
|
||||
{
|
||||
this.reverseBits = reverseBits;
|
||||
this.dwPolynomial = (uint) polynomial;
|
||||
this.GenerateLookupTable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the CRC-32 class - clear the CRC "remainder register."
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Use this when employing a single instance of this class to compute
|
||||
/// multiple, distinct CRCs on multiple, distinct data blocks.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public void Reset()
|
||||
{
|
||||
_register = 0xFFFFFFFFU;
|
||||
}
|
||||
|
||||
// private member vars
|
||||
private UInt32 dwPolynomial;
|
||||
private Int64 _TotalBytesRead;
|
||||
private bool reverseBits;
|
||||
private UInt32[] crc32Table;
|
||||
private const int BUFFER_SIZE = 8192;
|
||||
private UInt32 _register = 0xFFFFFFFFU;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,692 @@
|
|||
// DeflateStream.cs
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2009-2010 Dino Chiesa.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This code module is part of DotNetZip, a zipfile class library.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This code is licensed under the Microsoft Public License.
|
||||
// See the file License.txt for the license details.
|
||||
// More info on: http://dotnetzip.codeplex.com
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// last saved (in emacs):
|
||||
// Time-stamp: <2011-July-31 14:48:11>
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This module defines the DeflateStream class, which can be used as a replacement for
|
||||
// the System.IO.Compression.DeflateStream class in the .NET BCL.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
|
||||
using System;
|
||||
|
||||
namespace BestHTTP.Decompression.Zlib
|
||||
{
|
||||
/// <summary>
|
||||
/// A class for compressing and decompressing streams using the Deflate algorithm.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
///
|
||||
/// <para>
|
||||
/// The DeflateStream is a <see
|
||||
/// href="http://en.wikipedia.org/wiki/Decorator_pattern">Decorator</see> on a <see
|
||||
/// cref="System.IO.Stream"/>. It adds DEFLATE compression or decompression to any
|
||||
/// stream.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Using this stream, applications can compress or decompress data via stream
|
||||
/// <c>Read</c> and <c>Write</c> operations. Either compresssion or decompression
|
||||
/// can occur through either reading or writing. The compression format used is
|
||||
/// DEFLATE, which is documented in <see
|
||||
/// href="http://www.ietf.org/rfc/rfc1951.txt">IETF RFC 1951</see>, "DEFLATE
|
||||
/// Compressed Data Format Specification version 1.3.".
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
///
|
||||
/// <seealso cref="GZipStream" />
|
||||
public class DeflateStream : System.IO.Stream
|
||||
{
|
||||
internal ZlibBaseStream _baseStream;
|
||||
internal System.IO.Stream _innerStream;
|
||||
bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Create a DeflateStream using the specified CompressionMode.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// When mode is <c>CompressionMode.Compress</c>, the DeflateStream will use
|
||||
/// the default compression level. The "captive" stream will be closed when
|
||||
/// the DeflateStream is closed.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
/// This example uses a DeflateStream to compress data from a file, and writes
|
||||
/// the compressed data to another file.
|
||||
/// <code>
|
||||
/// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
|
||||
/// {
|
||||
/// using (var raw = System.IO.File.Create(fileToCompress + ".deflated"))
|
||||
/// {
|
||||
/// using (Stream compressor = new DeflateStream(raw, CompressionMode.Compress))
|
||||
/// {
|
||||
/// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
|
||||
/// int n;
|
||||
/// while ((n= input.Read(buffer, 0, buffer.Length)) != 0)
|
||||
/// {
|
||||
/// compressor.Write(buffer, 0, n);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <code lang="VB">
|
||||
/// Using input As Stream = File.OpenRead(fileToCompress)
|
||||
/// Using raw As FileStream = File.Create(fileToCompress & ".deflated")
|
||||
/// Using compressor As Stream = New DeflateStream(raw, CompressionMode.Compress)
|
||||
/// Dim buffer As Byte() = New Byte(4096) {}
|
||||
/// Dim n As Integer = -1
|
||||
/// Do While (n <> 0)
|
||||
/// If (n > 0) Then
|
||||
/// compressor.Write(buffer, 0, n)
|
||||
/// End If
|
||||
/// n = input.Read(buffer, 0, buffer.Length)
|
||||
/// Loop
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="stream">The stream which will be read or written.</param>
|
||||
/// <param name="mode">Indicates whether the DeflateStream will compress or decompress.</param>
|
||||
public DeflateStream(System.IO.Stream stream, CompressionMode mode)
|
||||
: this(stream, mode, CompressionLevel.Default, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a DeflateStream using the specified CompressionMode and the specified CompressionLevel.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
///
|
||||
/// <para>
|
||||
/// When mode is <c>CompressionMode.Decompress</c>, the level parameter is
|
||||
/// ignored. The "captive" stream will be closed when the DeflateStream is
|
||||
/// closed.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
///
|
||||
/// This example uses a DeflateStream to compress data from a file, and writes
|
||||
/// the compressed data to another file.
|
||||
///
|
||||
/// <code>
|
||||
/// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
|
||||
/// {
|
||||
/// using (var raw = System.IO.File.Create(fileToCompress + ".deflated"))
|
||||
/// {
|
||||
/// using (Stream compressor = new DeflateStream(raw,
|
||||
/// CompressionMode.Compress,
|
||||
/// CompressionLevel.BestCompression))
|
||||
/// {
|
||||
/// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
|
||||
/// int n= -1;
|
||||
/// while (n != 0)
|
||||
/// {
|
||||
/// if (n > 0)
|
||||
/// compressor.Write(buffer, 0, n);
|
||||
/// n= input.Read(buffer, 0, buffer.Length);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <code lang="VB">
|
||||
/// Using input As Stream = File.OpenRead(fileToCompress)
|
||||
/// Using raw As FileStream = File.Create(fileToCompress & ".deflated")
|
||||
/// Using compressor As Stream = New DeflateStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression)
|
||||
/// Dim buffer As Byte() = New Byte(4096) {}
|
||||
/// Dim n As Integer = -1
|
||||
/// Do While (n <> 0)
|
||||
/// If (n > 0) Then
|
||||
/// compressor.Write(buffer, 0, n)
|
||||
/// End If
|
||||
/// n = input.Read(buffer, 0, buffer.Length)
|
||||
/// Loop
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="stream">The stream to be read or written while deflating or inflating.</param>
|
||||
/// <param name="mode">Indicates whether the <c>DeflateStream</c> will compress or decompress.</param>
|
||||
/// <param name="level">A tuning knob to trade speed for effectiveness.</param>
|
||||
public DeflateStream(System.IO.Stream stream, CompressionMode mode, CompressionLevel level)
|
||||
: this(stream, mode, level, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <c>DeflateStream</c> using the specified
|
||||
/// <c>CompressionMode</c>, and explicitly specify whether the
|
||||
/// stream should be left open after Deflation or Inflation.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
///
|
||||
/// <para>
|
||||
/// This constructor allows the application to request that the captive stream
|
||||
/// remain open after the deflation or inflation occurs. By default, after
|
||||
/// <c>Close()</c> is called on the stream, the captive stream is also
|
||||
/// closed. In some cases this is not desired, for example if the stream is a
|
||||
/// memory stream that will be re-read after compression. Specify true for
|
||||
/// the <paramref name="leaveOpen"/> parameter to leave the stream open.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The <c>DeflateStream</c> will use the default compression level.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// See the other overloads of this constructor for example code.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
///
|
||||
/// <param name="stream">
|
||||
/// The stream which will be read or written. This is called the
|
||||
/// "captive" stream in other places in this documentation.
|
||||
/// </param>
|
||||
///
|
||||
/// <param name="mode">
|
||||
/// Indicates whether the <c>DeflateStream</c> will compress or decompress.
|
||||
/// </param>
|
||||
///
|
||||
/// <param name="leaveOpen">true if the application would like the stream to
|
||||
/// remain open after inflation/deflation.</param>
|
||||
public DeflateStream(System.IO.Stream stream, CompressionMode mode, bool leaveOpen)
|
||||
: this(stream, mode, CompressionLevel.Default, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <c>DeflateStream</c> using the specified <c>CompressionMode</c>
|
||||
/// and the specified <c>CompressionLevel</c>, and explicitly specify whether
|
||||
/// the stream should be left open after Deflation or Inflation.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
///
|
||||
/// <para>
|
||||
/// When mode is <c>CompressionMode.Decompress</c>, the level parameter is ignored.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// This constructor allows the application to request that the captive stream
|
||||
/// remain open after the deflation or inflation occurs. By default, after
|
||||
/// <c>Close()</c> is called on the stream, the captive stream is also
|
||||
/// closed. In some cases this is not desired, for example if the stream is a
|
||||
/// <see cref="System.IO.MemoryStream"/> that will be re-read after
|
||||
/// compression. Specify true for the <paramref name="leaveOpen"/> parameter
|
||||
/// to leave the stream open.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
///
|
||||
/// This example shows how to use a <c>DeflateStream</c> to compress data from
|
||||
/// a file, and store the compressed data into another file.
|
||||
///
|
||||
/// <code>
|
||||
/// using (var output = System.IO.File.Create(fileToCompress + ".deflated"))
|
||||
/// {
|
||||
/// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
|
||||
/// {
|
||||
/// using (Stream compressor = new DeflateStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, true))
|
||||
/// {
|
||||
/// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
|
||||
/// int n= -1;
|
||||
/// while (n != 0)
|
||||
/// {
|
||||
/// if (n > 0)
|
||||
/// compressor.Write(buffer, 0, n);
|
||||
/// n= input.Read(buffer, 0, buffer.Length);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// // can write additional data to the output stream here
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <code lang="VB">
|
||||
/// Using output As FileStream = File.Create(fileToCompress & ".deflated")
|
||||
/// Using input As Stream = File.OpenRead(fileToCompress)
|
||||
/// Using compressor As Stream = New DeflateStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, True)
|
||||
/// Dim buffer As Byte() = New Byte(4096) {}
|
||||
/// Dim n As Integer = -1
|
||||
/// Do While (n <> 0)
|
||||
/// If (n > 0) Then
|
||||
/// compressor.Write(buffer, 0, n)
|
||||
/// End If
|
||||
/// n = input.Read(buffer, 0, buffer.Length)
|
||||
/// Loop
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// ' can write additional data to the output stream here.
|
||||
/// End Using
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="stream">The stream which will be read or written.</param>
|
||||
/// <param name="mode">Indicates whether the DeflateStream will compress or decompress.</param>
|
||||
/// <param name="leaveOpen">true if the application would like the stream to remain open after inflation/deflation.</param>
|
||||
/// <param name="level">A tuning knob to trade speed for effectiveness.</param>
|
||||
public DeflateStream(System.IO.Stream stream, CompressionMode mode, CompressionLevel level, bool leaveOpen)
|
||||
{
|
||||
_innerStream = stream;
|
||||
_baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.DEFLATE, leaveOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <c>DeflateStream</c> using the specified <c>CompressionMode</c>
|
||||
/// and the specified <c>CompressionLevel</c>, and explicitly specify whether
|
||||
/// the stream should be left open after Deflation or Inflation.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
///
|
||||
/// <para>
|
||||
/// When mode is <c>CompressionMode.Decompress</c>, the level parameter is ignored.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// This constructor allows the application to request that the captive stream
|
||||
/// remain open after the deflation or inflation occurs. By default, after
|
||||
/// <c>Close()</c> is called on the stream, the captive stream is also
|
||||
/// closed. In some cases this is not desired, for example if the stream is a
|
||||
/// <see cref="System.IO.MemoryStream"/> that will be re-read after
|
||||
/// compression. Specify true for the <paramref name="leaveOpen"/> parameter
|
||||
/// to leave the stream open.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
///
|
||||
/// This example shows how to use a <c>DeflateStream</c> to compress data from
|
||||
/// a file, and store the compressed data into another file.
|
||||
///
|
||||
/// <code>
|
||||
/// using (var output = System.IO.File.Create(fileToCompress + ".deflated"))
|
||||
/// {
|
||||
/// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
|
||||
/// {
|
||||
/// using (Stream compressor = new DeflateStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, true))
|
||||
/// {
|
||||
/// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
|
||||
/// int n= -1;
|
||||
/// while (n != 0)
|
||||
/// {
|
||||
/// if (n > 0)
|
||||
/// compressor.Write(buffer, 0, n);
|
||||
/// n= input.Read(buffer, 0, buffer.Length);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// // can write additional data to the output stream here
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <code lang="VB">
|
||||
/// Using output As FileStream = File.Create(fileToCompress & ".deflated")
|
||||
/// Using input As Stream = File.OpenRead(fileToCompress)
|
||||
/// Using compressor As Stream = New DeflateStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, True)
|
||||
/// Dim buffer As Byte() = New Byte(4096) {}
|
||||
/// Dim n As Integer = -1
|
||||
/// Do While (n <> 0)
|
||||
/// If (n > 0) Then
|
||||
/// compressor.Write(buffer, 0, n)
|
||||
/// End If
|
||||
/// n = input.Read(buffer, 0, buffer.Length)
|
||||
/// Loop
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// ' can write additional data to the output stream here.
|
||||
/// End Using
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="stream">The stream which will be read or written.</param>
|
||||
/// <param name="mode">Indicates whether the DeflateStream will compress or decompress.</param>
|
||||
/// <param name="leaveOpen">true if the application would like the stream to remain open after inflation/deflation.</param>
|
||||
/// <param name="level">A tuning knob to trade speed for effectiveness.</param>
|
||||
/// <param name="windowBits">Desired window bits.</param>
|
||||
public DeflateStream(System.IO.Stream stream, CompressionMode mode, CompressionLevel level, bool leaveOpen, int windowBits)
|
||||
{
|
||||
_innerStream = stream;
|
||||
_baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.DEFLATE, leaveOpen, windowBits);
|
||||
}
|
||||
|
||||
#region Zlib properties
|
||||
|
||||
/// <summary>
|
||||
/// This property sets the flush behavior on the stream.
|
||||
/// </summary>
|
||||
/// <remarks> See the ZLIB documentation for the meaning of the flush behavior.
|
||||
/// </remarks>
|
||||
virtual public FlushType FlushMode
|
||||
{
|
||||
get { return (this._baseStream._flushMode); }
|
||||
set
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("DeflateStream");
|
||||
this._baseStream._flushMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The size of the working buffer for the compression codec.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The working buffer is used for all stream operations. The default size is
|
||||
/// 1024 bytes. The minimum size is 128 bytes. You may get better performance
|
||||
/// with a larger buffer. Then again, you might not. You would have to test
|
||||
/// it.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Set this before the first call to <c>Read()</c> or <c>Write()</c> on the
|
||||
/// stream. If you try to set it afterwards, it will throw.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public int BufferSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return this._baseStream._bufferSize;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("DeflateStream");
|
||||
if (this._baseStream._workingBuffer != null)
|
||||
throw new ZlibException("The working buffer is already set.");
|
||||
if (value < ZlibConstants.WorkingBufferSizeMin)
|
||||
throw new ZlibException(String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value, ZlibConstants.WorkingBufferSizeMin));
|
||||
this._baseStream._bufferSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ZLIB strategy to be used during compression.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// By tweaking this parameter, you may be able to optimize the compression for
|
||||
/// data with particular characteristics.
|
||||
/// </remarks>
|
||||
public CompressionStrategy Strategy
|
||||
{
|
||||
get
|
||||
{
|
||||
return this._baseStream.Strategy;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("DeflateStream");
|
||||
this._baseStream.Strategy = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Returns the total number of bytes input so far.</summary>
|
||||
virtual public long TotalIn
|
||||
{
|
||||
get
|
||||
{
|
||||
return this._baseStream._z.TotalBytesIn;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Returns the total number of bytes output so far.</summary>
|
||||
virtual public long TotalOut
|
||||
{
|
||||
get
|
||||
{
|
||||
return this._baseStream._z.TotalBytesOut;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region System.IO.Stream methods
|
||||
/// <summary>
|
||||
/// Dispose the stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This may or may not result in a <c>Close()</c> call on the captive
|
||||
/// stream. See the constructors that have a <c>leaveOpen</c> parameter
|
||||
/// for more information.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Application code won't call this code directly. This method may be
|
||||
/// invoked in two distinct scenarios. If disposing == true, the method
|
||||
/// has been called directly or indirectly by a user's code, for example
|
||||
/// via the public Dispose() method. In this case, both managed and
|
||||
/// unmanaged resources can be referenced and disposed. If disposing ==
|
||||
/// false, the method has been called by the runtime from inside the
|
||||
/// object finalizer and this method should not reference other objects;
|
||||
/// in that case only unmanaged resources must be referenced or
|
||||
/// disposed.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="disposing">
|
||||
/// true if the Dispose method was invoked by user code.
|
||||
/// </param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing && (this._baseStream != null))
|
||||
this._baseStream.Close();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the stream can be read.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The return value depends on whether the captive stream supports reading.
|
||||
/// </remarks>
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("DeflateStream");
|
||||
return _baseStream._stream.CanRead;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the stream supports Seek operations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Always returns false.
|
||||
/// </remarks>
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the stream can be written.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The return value depends on whether the captive stream supports writing.
|
||||
/// </remarks>
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("DeflateStream");
|
||||
return _baseStream._stream.CanWrite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush the stream.
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("DeflateStream");
|
||||
_baseStream.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reading this property always throws a <see cref="NotImplementedException"/>.
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The position of the stream pointer.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Setting this property always throws a <see
|
||||
/// cref="NotImplementedException"/>. Reading will return the total bytes
|
||||
/// written out, if used in writing, or the total bytes read in, if used in
|
||||
/// reading. The count may refer to compressed bytes or uncompressed bytes,
|
||||
/// depending on how you've used the stream.
|
||||
/// </remarks>
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this._baseStream._streamMode == BestHTTP.Decompression.Zlib.ZlibBaseStream.StreamMode.Writer)
|
||||
return this._baseStream._z.TotalBytesOut;
|
||||
if (this._baseStream._streamMode == BestHTTP.Decompression.Zlib.ZlibBaseStream.StreamMode.Reader)
|
||||
return this._baseStream._z.TotalBytesIn;
|
||||
return 0;
|
||||
}
|
||||
set { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read data from the stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// <para>
|
||||
/// If you wish to use the <c>DeflateStream</c> to compress data while
|
||||
/// reading, you can create a <c>DeflateStream</c> with
|
||||
/// <c>CompressionMode.Compress</c>, providing an uncompressed data stream.
|
||||
/// Then call Read() on that <c>DeflateStream</c>, and the data read will be
|
||||
/// compressed as you read. If you wish to use the <c>DeflateStream</c> to
|
||||
/// decompress data while reading, you can create a <c>DeflateStream</c> with
|
||||
/// <c>CompressionMode.Decompress</c>, providing a readable compressed data
|
||||
/// stream. Then call Read() on that <c>DeflateStream</c>, and the data read
|
||||
/// will be decompressed as you read.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// A <c>DeflateStream</c> can be used for <c>Read()</c> or <c>Write()</c>, but not both.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
/// <param name="buffer">The buffer into which the read data should be placed.</param>
|
||||
/// <param name="offset">the offset within that data array to put the first byte read.</param>
|
||||
/// <param name="count">the number of bytes to read.</param>
|
||||
/// <returns>the number of bytes actually read</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("DeflateStream");
|
||||
return _baseStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calling this method always throws a <see cref="NotImplementedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="offset">this is irrelevant, since it will always throw!</param>
|
||||
/// <param name="origin">this is irrelevant, since it will always throw!</param>
|
||||
/// <returns>irrelevant!</returns>
|
||||
public override long Seek(long offset, System.IO.SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will call the base stream's SetLength method.
|
||||
/// </summary>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
_baseStream.SetLength(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write data to the stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// <para>
|
||||
/// If you wish to use the <c>DeflateStream</c> to compress data while
|
||||
/// writing, you can create a <c>DeflateStream</c> with
|
||||
/// <c>CompressionMode.Compress</c>, and a writable output stream. Then call
|
||||
/// <c>Write()</c> on that <c>DeflateStream</c>, providing uncompressed data
|
||||
/// as input. The data sent to the output stream will be the compressed form
|
||||
/// of the data written. If you wish to use the <c>DeflateStream</c> to
|
||||
/// decompress data while writing, you can create a <c>DeflateStream</c> with
|
||||
/// <c>CompressionMode.Decompress</c>, and a writable output stream. Then
|
||||
/// call <c>Write()</c> on that stream, providing previously compressed
|
||||
/// data. The data sent to the output stream will be the decompressed form of
|
||||
/// the data written.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// A <c>DeflateStream</c> can be used for <c>Read()</c> or <c>Write()</c>,
|
||||
/// but not both.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
///
|
||||
/// <param name="buffer">The buffer holding data to write to the stream.</param>
|
||||
/// <param name="offset">the offset within that data array to find the first byte to write.</param>
|
||||
/// <param name="count">the number of bytes to write.</param>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("DeflateStream");
|
||||
_baseStream.Write(buffer, offset, count);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
using System;
|
||||
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
|
||||
namespace BestHTTP.Decompression
|
||||
{
|
||||
public sealed class GZipDecompressor : IDecompressor
|
||||
{
|
||||
private BufferPoolMemoryStream decompressorInputStream;
|
||||
private BufferPoolMemoryStream decompressorOutputStream;
|
||||
private Zlib.GZipStream decompressorStream;
|
||||
|
||||
private int MinLengthToDecompress = 256;
|
||||
|
||||
public GZipDecompressor(int minLengthToDecompress)
|
||||
{
|
||||
this.MinLengthToDecompress = minLengthToDecompress;
|
||||
}
|
||||
|
||||
private void CloseDecompressors()
|
||||
{
|
||||
if (decompressorStream != null)
|
||||
decompressorStream.Dispose();
|
||||
decompressorStream = null;
|
||||
|
||||
if (decompressorInputStream != null)
|
||||
decompressorInputStream.Dispose();
|
||||
decompressorInputStream = null;
|
||||
|
||||
if (decompressorOutputStream != null)
|
||||
decompressorOutputStream.Dispose();
|
||||
decompressorOutputStream = null;
|
||||
}
|
||||
|
||||
public DecompressedData Decompress(byte[] data, int offset, int count, bool forceDecompress = false, bool dataCanBeLarger = false)
|
||||
{
|
||||
if (decompressorInputStream == null)
|
||||
decompressorInputStream = new BufferPoolMemoryStream(count);
|
||||
|
||||
if (data != null)
|
||||
decompressorInputStream.Write(data, offset, count);
|
||||
|
||||
if (!forceDecompress && decompressorInputStream.Length < MinLengthToDecompress)
|
||||
return new DecompressedData(null, 0);
|
||||
|
||||
decompressorInputStream.Position = 0;
|
||||
|
||||
if (decompressorStream == null)
|
||||
{
|
||||
decompressorStream = new Zlib.GZipStream(decompressorInputStream,
|
||||
Zlib.CompressionMode.Decompress,
|
||||
Zlib.CompressionLevel.Default,
|
||||
true);
|
||||
decompressorStream.FlushMode = Zlib.FlushType.Sync;
|
||||
}
|
||||
|
||||
if (decompressorOutputStream == null)
|
||||
decompressorOutputStream = new BufferPoolMemoryStream();
|
||||
decompressorOutputStream.SetLength(0);
|
||||
|
||||
byte[] copyBuffer = BufferPool.Get(1024, true);
|
||||
|
||||
int readCount;
|
||||
int sumReadCount = 0;
|
||||
while ((readCount = decompressorStream.Read(copyBuffer, 0, copyBuffer.Length)) != 0)
|
||||
{
|
||||
decompressorOutputStream.Write(copyBuffer, 0, readCount);
|
||||
sumReadCount += readCount;
|
||||
}
|
||||
|
||||
BufferPool.Release(copyBuffer);
|
||||
|
||||
// If no read is done (returned with any data) don't zero out the input stream, as it would delete any not yet used data.
|
||||
if (sumReadCount > 0)
|
||||
decompressorStream.SetLength(0);
|
||||
|
||||
byte[] result = decompressorOutputStream.ToArray(dataCanBeLarger);
|
||||
|
||||
return new DecompressedData(result, dataCanBeLarger ? (int)decompressorOutputStream.Length : result.Length);
|
||||
}
|
||||
|
||||
~GZipDecompressor()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CloseDecompressors();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,920 @@
|
|||
// GZipStream.cs
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This code module is part of DotNetZip, a zipfile class library.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This code is licensed under the Microsoft Public License.
|
||||
// See the file License.txt for the license details.
|
||||
// More info on: http://dotnetzip.codeplex.com
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// last saved (in emacs):
|
||||
// Time-stamp: <2011-July-11 21:42:34>
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This module defines the GZipStream class, which can be used as a replacement for
|
||||
// the System.IO.Compression.GZipStream class in the .NET BCL. NB: The design is not
|
||||
// completely OO clean: there is some intelligence in the ZlibBaseStream that reads the
|
||||
// GZip header.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace BestHTTP.Decompression.Zlib
|
||||
{
|
||||
/// <summary>
|
||||
/// A class for compressing and decompressing GZIP streams.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// <para>
|
||||
/// The <c>GZipStream</c> is a <see
|
||||
/// href="http://en.wikipedia.org/wiki/Decorator_pattern">Decorator</see> on a
|
||||
/// <see cref="Stream"/>. It adds GZIP compression or decompression to any
|
||||
/// stream.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Like the <c>System.IO.Compression.GZipStream</c> in the .NET Base Class Library, the
|
||||
/// <c>Ionic.Zlib.GZipStream</c> can compress while writing, or decompress while
|
||||
/// reading, but not vice versa. The compression method used is GZIP, which is
|
||||
/// documented in <see href="http://www.ietf.org/rfc/rfc1952.txt">IETF RFC
|
||||
/// 1952</see>, "GZIP file format specification version 4.3".</para>
|
||||
///
|
||||
/// <para>
|
||||
/// A <c>GZipStream</c> can be used to decompress data (through <c>Read()</c>) or
|
||||
/// to compress data (through <c>Write()</c>), but not both.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// If you wish to use the <c>GZipStream</c> to compress data, you must wrap it
|
||||
/// around a write-able stream. As you call <c>Write()</c> on the <c>GZipStream</c>, the
|
||||
/// data will be compressed into the GZIP format. If you want to decompress data,
|
||||
/// you must wrap the <c>GZipStream</c> around a readable stream that contains an
|
||||
/// IETF RFC 1952-compliant stream. The data will be decompressed as you call
|
||||
/// <c>Read()</c> on the <c>GZipStream</c>.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Though the GZIP format allows data from multiple files to be concatenated
|
||||
/// together, this stream handles only a single segment of GZIP format, typically
|
||||
/// representing a single file.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
///
|
||||
/// <seealso cref="DeflateStream" />
|
||||
public class GZipStream : System.IO.Stream
|
||||
{
|
||||
// GZip format
|
||||
// source: http://tools.ietf.org/html/rfc1952
|
||||
//
|
||||
// header id: 2 bytes 1F 8B
|
||||
// compress method 1 byte 8= DEFLATE (none other supported)
|
||||
// flag 1 byte bitfield (See below)
|
||||
// mtime 4 bytes time_t (seconds since jan 1, 1970 UTC of the file.
|
||||
// xflg 1 byte 2 = max compress used , 4 = max speed (can be ignored)
|
||||
// OS 1 byte OS for originating archive. set to 0xFF in compression.
|
||||
// extra field length 2 bytes optional - only if FEXTRA is set.
|
||||
// extra field varies
|
||||
// filename varies optional - if FNAME is set. zero terminated. ISO-8859-1.
|
||||
// file comment varies optional - if FCOMMENT is set. zero terminated. ISO-8859-1.
|
||||
// crc16 1 byte optional - present only if FHCRC bit is set
|
||||
// compressed data varies
|
||||
// CRC32 4 bytes
|
||||
// isize 4 bytes data size modulo 2^32
|
||||
//
|
||||
// FLG (FLaGs)
|
||||
// bit 0 FTEXT - indicates file is ASCII text (can be safely ignored)
|
||||
// bit 1 FHCRC - there is a CRC16 for the header immediately following the header
|
||||
// bit 2 FEXTRA - extra fields are present
|
||||
// bit 3 FNAME - the zero-terminated filename is present. encoding; ISO-8859-1.
|
||||
// bit 4 FCOMMENT - a zero-terminated file comment is present. encoding: ISO-8859-1
|
||||
// bit 5 reserved
|
||||
// bit 6 reserved
|
||||
// bit 7 reserved
|
||||
//
|
||||
// On consumption:
|
||||
// Extra field is a bunch of nonsense and can be safely ignored.
|
||||
// Header CRC and OS, likewise.
|
||||
//
|
||||
// on generation:
|
||||
// all optional fields get 0, except for the OS, which gets 255.
|
||||
//
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The comment on the GZIP stream.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The GZIP format allows for each file to optionally have an associated
|
||||
/// comment stored with the file. The comment is encoded with the ISO-8859-1
|
||||
/// code page. To include a comment in a GZIP stream you create, set this
|
||||
/// property before calling <c>Write()</c> for the first time on the
|
||||
/// <c>GZipStream</c>.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// When using <c>GZipStream</c> to decompress, you can retrieve this property
|
||||
/// after the first call to <c>Read()</c>. If no comment has been set in the
|
||||
/// GZIP bytestream, the Comment property will return <c>null</c>
|
||||
/// (<c>Nothing</c> in VB).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public String Comment
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Comment;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("GZipStream");
|
||||
_Comment = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The FileName for the GZIP stream.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
///
|
||||
/// <para>
|
||||
/// The GZIP format optionally allows each file to have an associated
|
||||
/// filename. When compressing data (through <c>Write()</c>), set this
|
||||
/// FileName before calling <c>Write()</c> the first time on the <c>GZipStream</c>.
|
||||
/// The actual filename is encoded into the GZIP bytestream with the
|
||||
/// ISO-8859-1 code page, according to RFC 1952. It is the application's
|
||||
/// responsibility to insure that the FileName can be encoded and decoded
|
||||
/// correctly with this code page.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// When decompressing (through <c>Read()</c>), you can retrieve this value
|
||||
/// any time after the first <c>Read()</c>. In the case where there was no filename
|
||||
/// encoded into the GZIP bytestream, the property will return <c>null</c> (<c>Nothing</c>
|
||||
/// in VB).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public String FileName
|
||||
{
|
||||
get { return _FileName; }
|
||||
set
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("GZipStream");
|
||||
_FileName = value;
|
||||
if (_FileName == null) return;
|
||||
if (_FileName.IndexOf("/") != -1)
|
||||
{
|
||||
_FileName = _FileName.Replace("/", "\\");
|
||||
}
|
||||
if (_FileName.EndsWith("\\"))
|
||||
throw new Exception("Illegal filename");
|
||||
if (_FileName.IndexOf("\\") != -1)
|
||||
{
|
||||
// trim any leading path
|
||||
_FileName = Path.GetFileName(_FileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The last modified time for the GZIP stream.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// GZIP allows the storage of a last modified time with each GZIP entity.
|
||||
/// When compressing data, you can set this before the first call to
|
||||
/// <c>Write()</c>. When decompressing, you can retrieve this value any time
|
||||
/// after the first call to <c>Read()</c>.
|
||||
/// </remarks>
|
||||
public DateTime? LastModified;
|
||||
|
||||
/// <summary>
|
||||
/// The CRC on the GZIP stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used for internal error checking. You probably don't need to look at this property.
|
||||
/// </remarks>
|
||||
public int Crc32 { get { return _Crc32; } }
|
||||
|
||||
private int _headerByteCount;
|
||||
internal ZlibBaseStream _baseStream;
|
||||
bool _disposed;
|
||||
bool _firstReadDone;
|
||||
string _FileName;
|
||||
string _Comment;
|
||||
int _Crc32;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a <c>GZipStream</c> using the specified <c>CompressionMode</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// <para>
|
||||
/// When mode is <c>CompressionMode.Compress</c>, the <c>GZipStream</c> will use the
|
||||
/// default compression level.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// As noted in the class documentation, the <c>CompressionMode</c> (Compress
|
||||
/// or Decompress) also establishes the "direction" of the stream. A
|
||||
/// <c>GZipStream</c> with <c>CompressionMode.Compress</c> works only through
|
||||
/// <c>Write()</c>. A <c>GZipStream</c> with
|
||||
/// <c>CompressionMode.Decompress</c> works only through <c>Read()</c>.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
/// This example shows how to use a GZipStream to compress data.
|
||||
/// <code>
|
||||
/// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
|
||||
/// {
|
||||
/// using (var raw = System.IO.File.Create(outputFile))
|
||||
/// {
|
||||
/// using (Stream compressor = new GZipStream(raw, CompressionMode.Compress))
|
||||
/// {
|
||||
/// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
|
||||
/// int n;
|
||||
/// while ((n= input.Read(buffer, 0, buffer.Length)) != 0)
|
||||
/// {
|
||||
/// compressor.Write(buffer, 0, n);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// <code lang="VB">
|
||||
/// Dim outputFile As String = (fileToCompress & ".compressed")
|
||||
/// Using input As Stream = File.OpenRead(fileToCompress)
|
||||
/// Using raw As FileStream = File.Create(outputFile)
|
||||
/// Using compressor As Stream = New GZipStream(raw, CompressionMode.Compress)
|
||||
/// Dim buffer As Byte() = New Byte(4096) {}
|
||||
/// Dim n As Integer = -1
|
||||
/// Do While (n <> 0)
|
||||
/// If (n > 0) Then
|
||||
/// compressor.Write(buffer, 0, n)
|
||||
/// End If
|
||||
/// n = input.Read(buffer, 0, buffer.Length)
|
||||
/// Loop
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// <example>
|
||||
/// This example shows how to use a GZipStream to uncompress a file.
|
||||
/// <code>
|
||||
/// private void GunZipFile(string filename)
|
||||
/// {
|
||||
/// if (!filename.EndsWith(".gz))
|
||||
/// throw new ArgumentException("filename");
|
||||
/// var DecompressedFile = filename.Substring(0,filename.Length-3);
|
||||
/// byte[] working = new byte[WORKING_BUFFER_SIZE];
|
||||
/// int n= 1;
|
||||
/// using (System.IO.Stream input = System.IO.File.OpenRead(filename))
|
||||
/// {
|
||||
/// using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true))
|
||||
/// {
|
||||
/// using (var output = System.IO.File.Create(DecompressedFile))
|
||||
/// {
|
||||
/// while (n !=0)
|
||||
/// {
|
||||
/// n= decompressor.Read(working, 0, working.Length);
|
||||
/// if (n > 0)
|
||||
/// {
|
||||
/// output.Write(working, 0, n);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <code lang="VB">
|
||||
/// Private Sub GunZipFile(ByVal filename as String)
|
||||
/// If Not (filename.EndsWith(".gz)) Then
|
||||
/// Throw New ArgumentException("filename")
|
||||
/// End If
|
||||
/// Dim DecompressedFile as String = filename.Substring(0,filename.Length-3)
|
||||
/// Dim working(WORKING_BUFFER_SIZE) as Byte
|
||||
/// Dim n As Integer = 1
|
||||
/// Using input As Stream = File.OpenRead(filename)
|
||||
/// Using decompressor As Stream = new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, True)
|
||||
/// Using output As Stream = File.Create(UncompressedFile)
|
||||
/// Do
|
||||
/// n= decompressor.Read(working, 0, working.Length)
|
||||
/// If n > 0 Then
|
||||
/// output.Write(working, 0, n)
|
||||
/// End IF
|
||||
/// Loop While (n > 0)
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// End Sub
|
||||
/// </code>
|
||||
/// </example>
|
||||
///
|
||||
/// <param name="stream">The stream which will be read or written.</param>
|
||||
/// <param name="mode">Indicates whether the GZipStream will compress or decompress.</param>
|
||||
public GZipStream(Stream stream, CompressionMode mode)
|
||||
: this(stream, mode, CompressionLevel.Default, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <c>GZipStream</c> using the specified <c>CompressionMode</c> and
|
||||
/// the specified <c>CompressionLevel</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// <para>
|
||||
/// The <c>CompressionMode</c> (Compress or Decompress) also establishes the
|
||||
/// "direction" of the stream. A <c>GZipStream</c> with
|
||||
/// <c>CompressionMode.Compress</c> works only through <c>Write()</c>. A
|
||||
/// <c>GZipStream</c> with <c>CompressionMode.Decompress</c> works only
|
||||
/// through <c>Read()</c>.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
///
|
||||
/// This example shows how to use a <c>GZipStream</c> to compress a file into a .gz file.
|
||||
///
|
||||
/// <code>
|
||||
/// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
|
||||
/// {
|
||||
/// using (var raw = System.IO.File.Create(fileToCompress + ".gz"))
|
||||
/// {
|
||||
/// using (Stream compressor = new GZipStream(raw,
|
||||
/// CompressionMode.Compress,
|
||||
/// CompressionLevel.BestCompression))
|
||||
/// {
|
||||
/// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
|
||||
/// int n;
|
||||
/// while ((n= input.Read(buffer, 0, buffer.Length)) != 0)
|
||||
/// {
|
||||
/// compressor.Write(buffer, 0, n);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <code lang="VB">
|
||||
/// Using input As Stream = File.OpenRead(fileToCompress)
|
||||
/// Using raw As FileStream = File.Create(fileToCompress & ".gz")
|
||||
/// Using compressor As Stream = New GZipStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression)
|
||||
/// Dim buffer As Byte() = New Byte(4096) {}
|
||||
/// Dim n As Integer = -1
|
||||
/// Do While (n <> 0)
|
||||
/// If (n > 0) Then
|
||||
/// compressor.Write(buffer, 0, n)
|
||||
/// End If
|
||||
/// n = input.Read(buffer, 0, buffer.Length)
|
||||
/// Loop
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="stream">The stream to be read or written while deflating or inflating.</param>
|
||||
/// <param name="mode">Indicates whether the <c>GZipStream</c> will compress or decompress.</param>
|
||||
/// <param name="level">A tuning knob to trade speed for effectiveness.</param>
|
||||
public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level)
|
||||
: this(stream, mode, level, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <c>GZipStream</c> using the specified <c>CompressionMode</c>, and
|
||||
/// explicitly specify whether the stream should be left open after Deflation
|
||||
/// or Inflation.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This constructor allows the application to request that the captive stream
|
||||
/// remain open after the deflation or inflation occurs. By default, after
|
||||
/// <c>Close()</c> is called on the stream, the captive stream is also
|
||||
/// closed. In some cases this is not desired, for example if the stream is a
|
||||
/// memory stream that will be re-read after compressed data has been written
|
||||
/// to it. Specify true for the <paramref name="leaveOpen"/> parameter to leave
|
||||
/// the stream open.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The <see cref="CompressionMode"/> (Compress or Decompress) also
|
||||
/// establishes the "direction" of the stream. A <c>GZipStream</c> with
|
||||
/// <c>CompressionMode.Compress</c> works only through <c>Write()</c>. A <c>GZipStream</c>
|
||||
/// with <c>CompressionMode.Decompress</c> works only through <c>Read()</c>.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The <c>GZipStream</c> will use the default compression level. If you want
|
||||
/// to specify the compression level, see <see cref="GZipStream(Stream,
|
||||
/// CompressionMode, CompressionLevel, bool)"/>.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// See the other overloads of this constructor for example code.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
///
|
||||
/// <param name="stream">
|
||||
/// The stream which will be read or written. This is called the "captive"
|
||||
/// stream in other places in this documentation.
|
||||
/// </param>
|
||||
///
|
||||
/// <param name="mode">Indicates whether the GZipStream will compress or decompress.
|
||||
/// </param>
|
||||
///
|
||||
/// <param name="leaveOpen">
|
||||
/// true if the application would like the base stream to remain open after
|
||||
/// inflation/deflation.
|
||||
/// </param>
|
||||
public GZipStream(Stream stream, CompressionMode mode, bool leaveOpen)
|
||||
: this(stream, mode, CompressionLevel.Default, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <c>GZipStream</c> using the specified <c>CompressionMode</c> and the
|
||||
/// specified <c>CompressionLevel</c>, and explicitly specify whether the
|
||||
/// stream should be left open after Deflation or Inflation.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
///
|
||||
/// <para>
|
||||
/// This constructor allows the application to request that the captive stream
|
||||
/// remain open after the deflation or inflation occurs. By default, after
|
||||
/// <c>Close()</c> is called on the stream, the captive stream is also
|
||||
/// closed. In some cases this is not desired, for example if the stream is a
|
||||
/// memory stream that will be re-read after compressed data has been written
|
||||
/// to it. Specify true for the <paramref name="leaveOpen"/> parameter to
|
||||
/// leave the stream open.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// As noted in the class documentation, the <c>CompressionMode</c> (Compress
|
||||
/// or Decompress) also establishes the "direction" of the stream. A
|
||||
/// <c>GZipStream</c> with <c>CompressionMode.Compress</c> works only through
|
||||
/// <c>Write()</c>. A <c>GZipStream</c> with <c>CompressionMode.Decompress</c> works only
|
||||
/// through <c>Read()</c>.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
/// This example shows how to use a <c>GZipStream</c> to compress data.
|
||||
/// <code>
|
||||
/// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
|
||||
/// {
|
||||
/// using (var raw = System.IO.File.Create(outputFile))
|
||||
/// {
|
||||
/// using (Stream compressor = new GZipStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression, true))
|
||||
/// {
|
||||
/// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
|
||||
/// int n;
|
||||
/// while ((n= input.Read(buffer, 0, buffer.Length)) != 0)
|
||||
/// {
|
||||
/// compressor.Write(buffer, 0, n);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// <code lang="VB">
|
||||
/// Dim outputFile As String = (fileToCompress & ".compressed")
|
||||
/// Using input As Stream = File.OpenRead(fileToCompress)
|
||||
/// Using raw As FileStream = File.Create(outputFile)
|
||||
/// Using compressor As Stream = New GZipStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression, True)
|
||||
/// Dim buffer As Byte() = New Byte(4096) {}
|
||||
/// Dim n As Integer = -1
|
||||
/// Do While (n <> 0)
|
||||
/// If (n > 0) Then
|
||||
/// compressor.Write(buffer, 0, n)
|
||||
/// End If
|
||||
/// n = input.Read(buffer, 0, buffer.Length)
|
||||
/// Loop
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// End Using
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="stream">The stream which will be read or written.</param>
|
||||
/// <param name="mode">Indicates whether the GZipStream will compress or decompress.</param>
|
||||
/// <param name="leaveOpen">true if the application would like the stream to remain open after inflation/deflation.</param>
|
||||
/// <param name="level">A tuning knob to trade speed for effectiveness.</param>
|
||||
public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level, bool leaveOpen)
|
||||
{
|
||||
_baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, leaveOpen);
|
||||
}
|
||||
|
||||
#region Zlib properties
|
||||
|
||||
/// <summary>
|
||||
/// This property sets the flush behavior on the stream.
|
||||
/// </summary>
|
||||
virtual public FlushType FlushMode
|
||||
{
|
||||
get { return (this._baseStream._flushMode); }
|
||||
set {
|
||||
if (_disposed) throw new ObjectDisposedException("GZipStream");
|
||||
this._baseStream._flushMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The size of the working buffer for the compression codec.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The working buffer is used for all stream operations. The default size is
|
||||
/// 1024 bytes. The minimum size is 128 bytes. You may get better performance
|
||||
/// with a larger buffer. Then again, you might not. You would have to test
|
||||
/// it.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Set this before the first call to <c>Read()</c> or <c>Write()</c> on the
|
||||
/// stream. If you try to set it afterwards, it will throw.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public int BufferSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return this._baseStream._bufferSize;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("GZipStream");
|
||||
if (this._baseStream._workingBuffer != null)
|
||||
throw new ZlibException("The working buffer is already set.");
|
||||
if (value < ZlibConstants.WorkingBufferSizeMin)
|
||||
throw new ZlibException(String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value, ZlibConstants.WorkingBufferSizeMin));
|
||||
this._baseStream._bufferSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Returns the total number of bytes input so far.</summary>
|
||||
virtual public long TotalIn
|
||||
{
|
||||
get
|
||||
{
|
||||
return this._baseStream._z.TotalBytesIn;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Returns the total number of bytes output so far.</summary>
|
||||
virtual public long TotalOut
|
||||
{
|
||||
get
|
||||
{
|
||||
return this._baseStream._z.TotalBytesOut;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream methods
|
||||
|
||||
/// <summary>
|
||||
/// Dispose the stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This may or may not result in a <c>Close()</c> call on the captive
|
||||
/// stream. See the constructors that have a <c>leaveOpen</c> parameter
|
||||
/// for more information.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This method may be invoked in two distinct scenarios. If disposing
|
||||
/// == true, the method has been called directly or indirectly by a
|
||||
/// user's code, for example via the public Dispose() method. In this
|
||||
/// case, both managed and unmanaged resources can be referenced and
|
||||
/// disposed. If disposing == false, the method has been called by the
|
||||
/// runtime from inside the object finalizer and this method should not
|
||||
/// reference other objects; in that case only unmanaged resources must
|
||||
/// be referenced or disposed.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="disposing">
|
||||
/// indicates whether the Dispose method was invoked by user code.
|
||||
/// </param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing && (this._baseStream != null))
|
||||
{
|
||||
this._baseStream.Close();
|
||||
this._Crc32 = _baseStream.Crc32;
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the stream can be read.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The return value depends on whether the captive stream supports reading.
|
||||
/// </remarks>
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("GZipStream");
|
||||
return _baseStream._stream.CanRead;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the stream supports Seek operations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Always returns false.
|
||||
/// </remarks>
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the stream can be written.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The return value depends on whether the captive stream supports writing.
|
||||
/// </remarks>
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("GZipStream");
|
||||
return _baseStream._stream.CanWrite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush the stream.
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("GZipStream");
|
||||
_baseStream.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reading this property always throws a <see cref="NotImplementedException"/>.
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The position of the stream pointer.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Setting this property always throws a <see
|
||||
/// cref="NotImplementedException"/>. Reading will return the total bytes
|
||||
/// written out, if used in writing, or the total bytes read in, if used in
|
||||
/// reading. The count may refer to compressed bytes or uncompressed bytes,
|
||||
/// depending on how you've used the stream.
|
||||
/// </remarks>
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this._baseStream._streamMode == BestHTTP.Decompression.Zlib.ZlibBaseStream.StreamMode.Writer)
|
||||
return this._baseStream._z.TotalBytesOut + _headerByteCount;
|
||||
if (this._baseStream._streamMode == BestHTTP.Decompression.Zlib.ZlibBaseStream.StreamMode.Reader)
|
||||
return this._baseStream._z.TotalBytesIn + this._baseStream._gzipHeaderByteCount;
|
||||
return 0;
|
||||
}
|
||||
|
||||
set { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read and decompress data from the source stream.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// With a <c>GZipStream</c>, decompression is done through reading.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// byte[] working = new byte[WORKING_BUFFER_SIZE];
|
||||
/// using (System.IO.Stream input = System.IO.File.OpenRead(_CompressedFile))
|
||||
/// {
|
||||
/// using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true))
|
||||
/// {
|
||||
/// using (var output = System.IO.File.Create(_DecompressedFile))
|
||||
/// {
|
||||
/// int n;
|
||||
/// while ((n= decompressor.Read(working, 0, working.Length)) !=0)
|
||||
/// {
|
||||
/// output.Write(working, 0, n);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="buffer">The buffer into which the decompressed data should be placed.</param>
|
||||
/// <param name="offset">the offset within that data array to put the first byte read.</param>
|
||||
/// <param name="count">the number of bytes to read.</param>
|
||||
/// <returns>the number of bytes actually read</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("GZipStream");
|
||||
int n = _baseStream.Read(buffer, offset, count);
|
||||
|
||||
// Console.WriteLine("GZipStream::Read(buffer, off({0}), c({1}) = {2}", offset, count, n);
|
||||
// Console.WriteLine( Util.FormatByteArray(buffer, offset, n) );
|
||||
|
||||
if (!_firstReadDone)
|
||||
{
|
||||
_firstReadDone = true;
|
||||
FileName = _baseStream._GzipFileName;
|
||||
Comment = _baseStream._GzipComment;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calling this method always throws a <see cref="NotImplementedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="offset">irrelevant; it will always throw!</param>
|
||||
/// <param name="origin">irrelevant; it will always throw!</param>
|
||||
/// <returns>irrelevant!</returns>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calling this method always throws a <see cref="NotImplementedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">irrelevant; this method will always throw!</param>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
//throw new NotImplementedException();
|
||||
_baseStream.SetLength(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write data to the stream.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If you wish to use the <c>GZipStream</c> to compress data while writing,
|
||||
/// you can create a <c>GZipStream</c> with <c>CompressionMode.Compress</c>, and a
|
||||
/// writable output stream. Then call <c>Write()</c> on that <c>GZipStream</c>,
|
||||
/// providing uncompressed data as input. The data sent to the output stream
|
||||
/// will be the compressed form of the data written.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// A <c>GZipStream</c> can be used for <c>Read()</c> or <c>Write()</c>, but not
|
||||
/// both. Writing implies compression. Reading implies decompression.
|
||||
/// </para>
|
||||
///
|
||||
/// </remarks>
|
||||
/// <param name="buffer">The buffer holding data to write to the stream.</param>
|
||||
/// <param name="offset">the offset within that data array to find the first byte to write.</param>
|
||||
/// <param name="count">the number of bytes to write.</param>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("GZipStream");
|
||||
if (_baseStream._streamMode == BestHTTP.Decompression.Zlib.ZlibBaseStream.StreamMode.Undefined)
|
||||
{
|
||||
//Console.WriteLine("GZipStream: First write");
|
||||
if (_baseStream._wantCompress)
|
||||
{
|
||||
// first write in compression, therefore, emit the GZIP header
|
||||
_headerByteCount = EmitHeader();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
_baseStream.Write(buffer, offset, count);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
internal static readonly System.DateTime _unixEpoch = new System.DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
internal static readonly System.Text.Encoding iso8859dash1 = System.Text.Encoding.GetEncoding("iso-8859-1");
|
||||
|
||||
|
||||
private int EmitHeader()
|
||||
{
|
||||
byte[] commentBytes = (Comment == null) ? null : iso8859dash1.GetBytes(Comment);
|
||||
byte[] filenameBytes = (FileName == null) ? null : iso8859dash1.GetBytes(FileName);
|
||||
|
||||
int cbLength = (Comment == null) ? 0 : commentBytes.Length + 1;
|
||||
int fnLength = (FileName == null) ? 0 : filenameBytes.Length + 1;
|
||||
|
||||
int bufferLength = 10 + cbLength + fnLength;
|
||||
byte[] header = BufferPool.Get(bufferLength, true);
|
||||
int i = 0;
|
||||
// ID
|
||||
header[i++] = 0x1F;
|
||||
header[i++] = 0x8B;
|
||||
|
||||
// compression method
|
||||
header[i++] = 8;
|
||||
byte flag = 0;
|
||||
if (Comment != null)
|
||||
flag ^= 0x10;
|
||||
if (FileName != null)
|
||||
flag ^= 0x8;
|
||||
|
||||
// flag
|
||||
header[i++] = flag;
|
||||
|
||||
// mtime
|
||||
if (!LastModified.HasValue) LastModified = DateTime.Now;
|
||||
System.TimeSpan delta = LastModified.Value - _unixEpoch;
|
||||
Int32 timet = (Int32)delta.TotalSeconds;
|
||||
Array.Copy(BitConverter.GetBytes(timet), 0, header, i, 4);
|
||||
i += 4;
|
||||
|
||||
// xflg
|
||||
header[i++] = 0; // this field is totally useless
|
||||
// OS
|
||||
header[i++] = 0xFF; // 0xFF == unspecified
|
||||
|
||||
// extra field length - only if FEXTRA is set, which it is not.
|
||||
//header[i++]= 0;
|
||||
//header[i++]= 0;
|
||||
|
||||
// filename
|
||||
if (fnLength != 0)
|
||||
{
|
||||
Array.Copy(filenameBytes, 0, header, i, fnLength - 1);
|
||||
i += fnLength - 1;
|
||||
header[i++] = 0; // terminate
|
||||
}
|
||||
|
||||
// comment
|
||||
if (cbLength != 0)
|
||||
{
|
||||
Array.Copy(commentBytes, 0, header, i, cbLength - 1);
|
||||
i += cbLength - 1;
|
||||
header[i++] = 0; // terminate
|
||||
}
|
||||
|
||||
_baseStream._stream.Write(header, 0, i);
|
||||
int headerLength = header.Length;
|
||||
|
||||
BufferPool.Release(header);
|
||||
|
||||
return headerLength; // bytes written
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
using BestHTTP.Logger;
|
||||
|
||||
namespace BestHTTP.Decompression
|
||||
{
|
||||
public struct DecompressedData
|
||||
{
|
||||
public readonly byte[] Data;
|
||||
public readonly int Length;
|
||||
|
||||
internal DecompressedData(byte[] data, int length)
|
||||
{
|
||||
this.Data = data;
|
||||
this.Length = length;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDecompressor : IDisposable
|
||||
{
|
||||
DecompressedData Decompress(byte[] data, int offset, int count, bool forceDecompress = false, bool dataCanBeLarger = false);
|
||||
}
|
||||
|
||||
public static class DecompressorFactory
|
||||
{
|
||||
public const int MinLengthToDecompress = 256;
|
||||
|
||||
public static void SetupHeaders(HTTPRequest request)
|
||||
{
|
||||
if (!request.HasHeader("Accept-Encoding"))
|
||||
{
|
||||
#if BESTHTTP_DISABLE_GZIP
|
||||
request.AddHeader("Accept-Encoding", "identity");
|
||||
#else
|
||||
if (BrotliDecompressor.IsSupported())
|
||||
request.AddHeader("Accept-Encoding", "br, gzip, identity");
|
||||
else
|
||||
request.AddHeader("Accept-Encoding", "gzip, identity");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static IDecompressor GetDecompressor(string encoding, LoggingContext context)
|
||||
{
|
||||
if (encoding == null)
|
||||
return null;
|
||||
|
||||
switch (encoding.ToLowerInvariant())
|
||||
{
|
||||
case "identity":
|
||||
case "utf-8":
|
||||
break;
|
||||
|
||||
case "gzip": return new Decompression.GZipDecompressor(MinLengthToDecompress);
|
||||
|
||||
case "br":
|
||||
if (Decompression.BrotliDecompressor.IsSupported())
|
||||
return new Decompression.BrotliDecompressor(MinLengthToDecompress);
|
||||
else
|
||||
goto default;
|
||||
|
||||
default:
|
||||
HTTPManager.Logger.Warning("DecompressorFactory", "GetDecompressor - unsupported encoding: " + encoding, context);
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns with a properly set up GZip/Deflate/Brotli stream, or null if the encoding is null or compiled for WebGl.
|
||||
/// </summary>
|
||||
public static Stream GetDecoderStream(Stream streamToDecode, string encoding)
|
||||
{
|
||||
if (streamToDecode == null)
|
||||
throw new ArgumentNullException(nameof(streamToDecode));
|
||||
|
||||
if (string.IsNullOrEmpty(encoding))
|
||||
return null;
|
||||
|
||||
switch (encoding)
|
||||
{
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
case "gzip": return new Decompression.Zlib.GZipStream(streamToDecode, Decompression.Zlib.CompressionMode.Decompress);
|
||||
case "deflate": return new Decompression.Zlib.DeflateStream(streamToDecode, Decompression.Zlib.CompressionMode.Decompress);
|
||||
#if (NET_STANDARD_2_1 || UNITY_2021_2_OR_NEWER) && BESTHTTP_ENABLE_BROTLI
|
||||
case "br": return new System.IO.Compression.BrotliStream(streamToDecode, System.IO.Compression.CompressionMode.Decompress, true);
|
||||
#endif
|
||||
#endif
|
||||
//identity, utf-8, etc. Or compiled for WebGl.
|
||||
default:
|
||||
// Do not copy from one stream to an other, just return with the raw bytes
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,436 @@
|
|||
// Inftree.cs
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This code module is part of DotNetZip, a zipfile class library.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This code is licensed under the Microsoft Public License.
|
||||
// See the file License.txt for the license details.
|
||||
// More info on: http://dotnetzip.codeplex.com
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// last saved (in emacs):
|
||||
// Time-stamp: <2009-October-28 12:43:54>
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This module defines classes used in decompression. This code is derived
|
||||
// from the jzlib implementation of zlib. In keeping with the license for jzlib,
|
||||
// the copyright to that code is below.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in
|
||||
// the documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. The names of the authors may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
//
|
||||
// This program is based on zlib-1.1.3; credit to authors
|
||||
// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
|
||||
// and contributors of zlib.
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
using System;
|
||||
namespace BestHTTP.Decompression.Zlib
|
||||
{
|
||||
|
||||
sealed class InfTree
|
||||
{
|
||||
|
||||
private const int MANY = 1440;
|
||||
|
||||
private const int Z_OK = 0;
|
||||
private const int Z_STREAM_END = 1;
|
||||
private const int Z_NEED_DICT = 2;
|
||||
private const int Z_ERRNO = - 1;
|
||||
private const int Z_STREAM_ERROR = - 2;
|
||||
private const int Z_DATA_ERROR = - 3;
|
||||
private const int Z_MEM_ERROR = - 4;
|
||||
private const int Z_BUF_ERROR = - 5;
|
||||
private const int Z_VERSION_ERROR = - 6;
|
||||
|
||||
internal const int fixed_bl = 9;
|
||||
internal const int fixed_bd = 5;
|
||||
|
||||
//UPGRADE_NOTE: Final was removed from the declaration of 'fixed_tl'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
|
||||
internal static readonly int[] fixed_tl = new int[]{96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 192, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 160, 0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 224, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 144, 83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 208, 81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 176, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 240, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 200, 81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, 168, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 232, 80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 152, 84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, 216, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 184, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 248, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 196, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 164, 0, 8, 2, 0, 8, 130, 0, 8, 66, 0, 9, 228, 80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 148, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 212, 82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, 180, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 244, 80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 204, 81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 172, 0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 236, 80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 156, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 220, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 188, 0, 8, 14, 0, 8, 142, 0, 8, 78, 0, 9, 252, 96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131, 82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 194, 80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 162, 0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 226, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 146, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 210, 81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 178, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 242, 80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8, 117, 0, 8, 53, 0, 9, 202, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 170, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 234, 80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 154, 84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 218, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 186,
|
||||
0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 250, 80, 7, 3, 0, 8, 83, 0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 198, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 166, 0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 230, 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 150, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 214, 82, 7, 19, 0, 8, 107, 0, 8, 43, 0, 9, 182, 0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 246, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, 206, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 174, 0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 238, 80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 158, 84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 222, 82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 190, 0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 254, 96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 193, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 161, 0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 225, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 145, 83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 209, 81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 177, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 241, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 201, 81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, 169, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 233, 80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 153, 84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, 217, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 185, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 249, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 197, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 165, 0, 8, 2, 0, 8, 130, 0, 8, 66, 0, 9, 229, 80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 149, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 213, 82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, 181, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 245, 80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 205, 81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 173, 0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 237, 80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 157, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 221, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 189, 0, 8,
|
||||
14, 0, 8, 142, 0, 8, 78, 0, 9, 253, 96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131, 82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 195, 80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 163, 0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 227, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 147, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 211, 81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 179, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 243, 80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8, 117, 0, 8, 53, 0, 9, 203, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 171, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 235, 80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 155, 84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 219, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 187, 0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 251, 80, 7, 3, 0, 8, 83, 0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 199, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 167, 0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 231, 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 151, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 215, 82, 7, 19, 0, 8, 107, 0, 8, 43, 0, 9, 183, 0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 247, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, 207, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 175, 0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 239, 80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 159, 84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 223, 82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 191, 0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 255};
|
||||
//UPGRADE_NOTE: Final was removed from the declaration of 'fixed_td'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
|
||||
internal static readonly int[] fixed_td = new int[]{80, 5, 1, 87, 5, 257, 83, 5, 17, 91, 5, 4097, 81, 5, 5, 89, 5, 1025, 85, 5, 65, 93, 5, 16385, 80, 5, 3, 88, 5, 513, 84, 5, 33, 92, 5, 8193, 82, 5, 9, 90, 5, 2049, 86, 5, 129, 192, 5, 24577, 80, 5, 2, 87, 5, 385, 83, 5, 25, 91, 5, 6145, 81, 5, 7, 89, 5, 1537, 85, 5, 97, 93, 5, 24577, 80, 5, 4, 88, 5, 769, 84, 5, 49, 92, 5, 12289, 82, 5, 13, 90, 5, 3073, 86, 5, 193, 192, 5, 24577};
|
||||
|
||||
// Tables for deflate from PKZIP's appnote.txt.
|
||||
//UPGRADE_NOTE: Final was removed from the declaration of 'cplens'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
|
||||
internal static readonly int[] cplens = new int[]{3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0};
|
||||
|
||||
// see note #13 above about 258
|
||||
//UPGRADE_NOTE: Final was removed from the declaration of 'cplext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
|
||||
internal static readonly int[] cplext = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112};
|
||||
|
||||
//UPGRADE_NOTE: Final was removed from the declaration of 'cpdist'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
|
||||
internal static readonly int[] cpdist = new int[]{1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577};
|
||||
|
||||
//UPGRADE_NOTE: Final was removed from the declaration of 'cpdext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
|
||||
internal static readonly int[] cpdext = new int[]{0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
|
||||
|
||||
// If BMAX needs to be larger than 16, then h and x[] should be uLong.
|
||||
internal const int BMAX = 15; // maximum bit length of any code
|
||||
|
||||
internal int[] hn = null; // hufts used in space
|
||||
internal int[] v = null; // work area for huft_build
|
||||
internal int[] c = null; // bit length count table
|
||||
internal int[] r = null; // table entity for structure assignment
|
||||
internal int[] u = null; // table stack
|
||||
internal int[] x = null; // bit offsets, then code stack
|
||||
|
||||
private int huft_build(int[] b, int bindex, int n, int s, int[] d, int[] e, int[] t, int[] m, int[] hp, int[] hn, int[] v)
|
||||
{
|
||||
// Given a list of code lengths and a maximum table size, make a set of
|
||||
// tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR
|
||||
// if the given code set is incomplete (the tables are still built in this
|
||||
// case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of
|
||||
// lengths), or Z_MEM_ERROR if not enough memory.
|
||||
|
||||
int a; // counter for codes of length k
|
||||
int f; // i repeats in table every f entries
|
||||
int g; // maximum code length
|
||||
int h; // table level
|
||||
int i; // counter, current code
|
||||
int j; // counter
|
||||
int k; // number of bits in current code
|
||||
int l; // bits per table (returned in m)
|
||||
int mask; // (1 << w) - 1, to avoid cc -O bug on HP
|
||||
int p; // pointer into c[], b[], or v[]
|
||||
int q; // points to current table
|
||||
int w; // bits before this table == (l * h)
|
||||
int xp; // pointer into x
|
||||
int y; // number of dummy codes added
|
||||
int z; // number of entries in current table
|
||||
|
||||
// Generate counts for each bit length
|
||||
|
||||
p = 0; i = n;
|
||||
do
|
||||
{
|
||||
c[b[bindex + p]]++; p++; i--; // assume all entries <= BMAX
|
||||
}
|
||||
while (i != 0);
|
||||
|
||||
if (c[0] == n)
|
||||
{
|
||||
// null input--all zero length codes
|
||||
t[0] = - 1;
|
||||
m[0] = 0;
|
||||
return Z_OK;
|
||||
}
|
||||
|
||||
// Find minimum and maximum length, bound *m by those
|
||||
l = m[0];
|
||||
for (j = 1; j <= BMAX; j++)
|
||||
if (c[j] != 0)
|
||||
break;
|
||||
k = j; // minimum code length
|
||||
if (l < j)
|
||||
{
|
||||
l = j;
|
||||
}
|
||||
for (i = BMAX; i != 0; i--)
|
||||
{
|
||||
if (c[i] != 0)
|
||||
break;
|
||||
}
|
||||
g = i; // maximum code length
|
||||
if (l > i)
|
||||
{
|
||||
l = i;
|
||||
}
|
||||
m[0] = l;
|
||||
|
||||
// Adjust last length count to fill out codes, if needed
|
||||
for (y = 1 << j; j < i; j++, y <<= 1)
|
||||
{
|
||||
if ((y -= c[j]) < 0)
|
||||
{
|
||||
return Z_DATA_ERROR;
|
||||
}
|
||||
}
|
||||
if ((y -= c[i]) < 0)
|
||||
{
|
||||
return Z_DATA_ERROR;
|
||||
}
|
||||
c[i] += y;
|
||||
|
||||
// Generate starting offsets into the value table for each length
|
||||
x[1] = j = 0;
|
||||
p = 1; xp = 2;
|
||||
while (--i != 0)
|
||||
{
|
||||
// note that i == g from above
|
||||
x[xp] = (j += c[p]);
|
||||
xp++;
|
||||
p++;
|
||||
}
|
||||
|
||||
// Make a table of values in order of bit lengths
|
||||
i = 0; p = 0;
|
||||
do
|
||||
{
|
||||
if ((j = b[bindex + p]) != 0)
|
||||
{
|
||||
v[x[j]++] = i;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
while (++i < n);
|
||||
n = x[g]; // set n to length of v
|
||||
|
||||
// Generate the Huffman codes and for each, make the table entries
|
||||
x[0] = i = 0; // first Huffman code is zero
|
||||
p = 0; // grab values in bit order
|
||||
h = - 1; // no tables yet--level -1
|
||||
w = - l; // bits decoded == (l * h)
|
||||
u[0] = 0; // just to keep compilers happy
|
||||
q = 0; // ditto
|
||||
z = 0; // ditto
|
||||
|
||||
// go through the bit lengths (k already is bits in shortest code)
|
||||
for (; k <= g; k++)
|
||||
{
|
||||
a = c[k];
|
||||
while (a-- != 0)
|
||||
{
|
||||
// here i is the Huffman code of length k bits for value *p
|
||||
// make tables up to required level
|
||||
while (k > w + l)
|
||||
{
|
||||
h++;
|
||||
w += l; // previous table always l bits
|
||||
// compute minimum size table less than or equal to l bits
|
||||
z = g - w;
|
||||
z = (z > l)?l:z; // table size upper limit
|
||||
if ((f = 1 << (j = k - w)) > a + 1)
|
||||
{
|
||||
// try a k-w bit table
|
||||
// too few codes for k-w bit table
|
||||
f -= (a + 1); // deduct codes from patterns left
|
||||
xp = k;
|
||||
if (j < z)
|
||||
{
|
||||
while (++j < z)
|
||||
{
|
||||
// try smaller tables up to z bits
|
||||
if ((f <<= 1) <= c[++xp])
|
||||
break; // enough codes to use up j bits
|
||||
f -= c[xp]; // else deduct codes from patterns
|
||||
}
|
||||
}
|
||||
}
|
||||
z = 1 << j; // table entries for j-bit table
|
||||
|
||||
// allocate new table
|
||||
if (hn[0] + z > MANY)
|
||||
{
|
||||
// (note: doesn't matter for fixed)
|
||||
return Z_DATA_ERROR; // overflow of MANY
|
||||
}
|
||||
u[h] = q = hn[0]; // DEBUG
|
||||
hn[0] += z;
|
||||
|
||||
// connect to last table, if there is one
|
||||
if (h != 0)
|
||||
{
|
||||
x[h] = i; // save pattern for backing up
|
||||
r[0] = (sbyte) j; // bits in this table
|
||||
r[1] = (sbyte) l; // bits to dump before this table
|
||||
j = SharedUtils.URShift(i, (w - l));
|
||||
r[2] = (int) (q - u[h - 1] - j); // offset to this table
|
||||
Array.Copy(r, 0, hp, (u[h - 1] + j) * 3, 3); // connect to last table
|
||||
}
|
||||
else
|
||||
{
|
||||
t[0] = q; // first table is returned result
|
||||
}
|
||||
}
|
||||
|
||||
// set up table entity in r
|
||||
r[1] = (sbyte) (k - w);
|
||||
if (p >= n)
|
||||
{
|
||||
r[0] = 128 + 64; // out of values--invalid code
|
||||
}
|
||||
else if (v[p] < s)
|
||||
{
|
||||
r[0] = (sbyte) (v[p] < 256?0:32 + 64); // 256 is end-of-block
|
||||
r[2] = v[p++]; // simple code is just the value
|
||||
}
|
||||
else
|
||||
{
|
||||
r[0] = (sbyte) (e[v[p] - s] + 16 + 64); // non-simple--look up in lists
|
||||
r[2] = d[v[p++] - s];
|
||||
}
|
||||
|
||||
// fill code-like entries with r
|
||||
f = 1 << (k - w);
|
||||
for (j = SharedUtils.URShift(i, w); j < z; j += f)
|
||||
{
|
||||
Array.Copy(r, 0, hp, (q + j) * 3, 3);
|
||||
}
|
||||
|
||||
// backwards increment the k-bit code i
|
||||
for (j = 1 << (k - 1); (i & j) != 0; j = SharedUtils.URShift(j, 1))
|
||||
{
|
||||
i ^= j;
|
||||
}
|
||||
i ^= j;
|
||||
|
||||
// backup over finished tables
|
||||
mask = (1 << w) - 1; // needed on HP, cc -O bug
|
||||
while ((i & mask) != x[h])
|
||||
{
|
||||
h--; // don't need to update q
|
||||
w -= l;
|
||||
mask = (1 << w) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return Z_BUF_ERROR if we were given an incomplete table
|
||||
return y != 0 && g != 1?Z_BUF_ERROR:Z_OK;
|
||||
}
|
||||
|
||||
internal int inflate_trees_bits(int[] c, int[] bb, int[] tb, int[] hp, ZlibCodec z)
|
||||
{
|
||||
int result;
|
||||
initWorkArea(19);
|
||||
hn[0] = 0;
|
||||
result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v);
|
||||
|
||||
if (result == Z_DATA_ERROR)
|
||||
{
|
||||
z.Message = "oversubscribed dynamic bit lengths tree";
|
||||
}
|
||||
else if (result == Z_BUF_ERROR || bb[0] == 0)
|
||||
{
|
||||
z.Message = "incomplete dynamic bit lengths tree";
|
||||
result = Z_DATA_ERROR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal int inflate_trees_dynamic(int nl, int nd, int[] c, int[] bl, int[] bd, int[] tl, int[] td, int[] hp, ZlibCodec z)
|
||||
{
|
||||
int result;
|
||||
|
||||
// build literal/length tree
|
||||
initWorkArea(288);
|
||||
hn[0] = 0;
|
||||
result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v);
|
||||
if (result != Z_OK || bl[0] == 0)
|
||||
{
|
||||
if (result == Z_DATA_ERROR)
|
||||
{
|
||||
z.Message = "oversubscribed literal/length tree";
|
||||
}
|
||||
else if (result != Z_MEM_ERROR)
|
||||
{
|
||||
z.Message = "incomplete literal/length tree";
|
||||
result = Z_DATA_ERROR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// build distance tree
|
||||
initWorkArea(288);
|
||||
result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v);
|
||||
|
||||
if (result != Z_OK || (bd[0] == 0 && nl > 257))
|
||||
{
|
||||
if (result == Z_DATA_ERROR)
|
||||
{
|
||||
z.Message = "oversubscribed distance tree";
|
||||
}
|
||||
else if (result == Z_BUF_ERROR)
|
||||
{
|
||||
z.Message = "incomplete distance tree";
|
||||
result = Z_DATA_ERROR;
|
||||
}
|
||||
else if (result != Z_MEM_ERROR)
|
||||
{
|
||||
z.Message = "empty distance tree with lengths";
|
||||
result = Z_DATA_ERROR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return Z_OK;
|
||||
}
|
||||
|
||||
internal static int inflate_trees_fixed(int[] bl, int[] bd, int[][] tl, int[][] td, ZlibCodec z)
|
||||
{
|
||||
bl[0] = fixed_bl;
|
||||
bd[0] = fixed_bd;
|
||||
tl[0] = fixed_tl;
|
||||
td[0] = fixed_td;
|
||||
return Z_OK;
|
||||
}
|
||||
|
||||
private void initWorkArea(int vsize)
|
||||
{
|
||||
if (hn == null)
|
||||
{
|
||||
hn = new int[1];
|
||||
v = new int[vsize];
|
||||
c = new int[BMAX + 1];
|
||||
r = new int[3];
|
||||
u = new int[BMAX];
|
||||
x = new int[BMAX + 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v.Length < vsize)
|
||||
{
|
||||
v = new int[vsize];
|
||||
}
|
||||
Array.Clear(v,0,vsize);
|
||||
Array.Clear(c,0,BMAX+1);
|
||||
r[0]=0; r[1]=0; r[2]=0;
|
||||
// for(int i=0; i<BMAX; i++){u[i]=0;}
|
||||
//Array.Copy(c, 0, u, 0, BMAX);
|
||||
Array.Clear(u,0,BMAX);
|
||||
// for(int i=0; i<BMAX+1; i++){x[i]=0;}
|
||||
//Array.Copy(c, 0, x, 0, BMAX + 1);
|
||||
Array.Clear(x,0,BMAX+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,423 @@
|
|||
// Tree.cs
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This code module is part of DotNetZip, a zipfile class library.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This code is licensed under the Microsoft Public License.
|
||||
// See the file License.txt for the license details.
|
||||
// More info on: http://dotnetzip.codeplex.com
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// last saved (in emacs):
|
||||
// Time-stamp: <2009-October-28 13:29:50>
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This module defines classes for zlib compression and
|
||||
// decompression. This code is derived from the jzlib implementation of
|
||||
// zlib. In keeping with the license for jzlib, the copyright to that
|
||||
// code is below.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in
|
||||
// the documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. The names of the authors may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
//
|
||||
// This program is based on zlib-1.1.3; credit to authors
|
||||
// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
|
||||
// and contributors of zlib.
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
|
||||
using System;
|
||||
|
||||
namespace BestHTTP.Decompression.Zlib
|
||||
{
|
||||
sealed class ZTree
|
||||
{
|
||||
private static readonly int HEAP_SIZE = (2 * InternalConstants.L_CODES + 1);
|
||||
|
||||
// extra bits for each length code
|
||||
internal static readonly int[] ExtraLengthBits = new int[]
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
|
||||
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
|
||||
};
|
||||
|
||||
// extra bits for each distance code
|
||||
internal static readonly int[] ExtraDistanceBits = new int[]
|
||||
{
|
||||
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
|
||||
7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13
|
||||
};
|
||||
|
||||
// extra bits for each bit length code
|
||||
internal static readonly int[] extra_blbits = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7};
|
||||
|
||||
internal static readonly sbyte[] bl_order = new sbyte[]{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
|
||||
|
||||
|
||||
// The lengths of the bit length codes are sent in order of decreasing
|
||||
// probability, to avoid transmitting the lengths for unused bit
|
||||
// length codes.
|
||||
|
||||
internal const int Buf_size = 8 * 2;
|
||||
|
||||
// see definition of array dist_code below
|
||||
//internal const int DIST_CODE_LEN = 512;
|
||||
|
||||
private static readonly sbyte[] _dist_code = new sbyte[]
|
||||
{
|
||||
0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
|
||||
8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9,
|
||||
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
|
||||
11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
|
||||
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
|
||||
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
|
||||
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
|
||||
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
|
||||
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
|
||||
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
|
||||
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
|
||||
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
|
||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||
0, 0, 16, 17, 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21,
|
||||
22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
|
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
|
||||
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
|
||||
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
|
||||
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
|
||||
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
|
||||
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
|
||||
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
|
||||
};
|
||||
|
||||
internal static readonly sbyte[] LengthCode = new sbyte[]
|
||||
{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11,
|
||||
12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
|
||||
16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17,
|
||||
18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19,
|
||||
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
|
||||
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
|
||||
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
|
||||
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
|
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
|
||||
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
|
||||
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28
|
||||
};
|
||||
|
||||
|
||||
internal static readonly int[] LengthBase = new int[]
|
||||
{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28,
|
||||
32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 0
|
||||
};
|
||||
|
||||
|
||||
internal static readonly int[] DistanceBase = new int[]
|
||||
{
|
||||
0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192,
|
||||
256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576
|
||||
};
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Map from a distance to a distance code.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// No side effects. _dist_code[256] and _dist_code[257] are never used.
|
||||
/// </remarks>
|
||||
internal static int DistanceCode(int dist)
|
||||
{
|
||||
return (dist < 256)
|
||||
? _dist_code[dist]
|
||||
: _dist_code[256 + SharedUtils.URShift(dist, 7)];
|
||||
}
|
||||
|
||||
internal short[] dyn_tree; // the dynamic tree
|
||||
internal int max_code; // largest code with non zero frequency
|
||||
internal StaticTree staticTree; // the corresponding static tree
|
||||
|
||||
// Compute the optimal bit lengths for a tree and update the total bit length
|
||||
// for the current block.
|
||||
// IN assertion: the fields freq and dad are set, heap[heap_max] and
|
||||
// above are the tree nodes sorted by increasing frequency.
|
||||
// OUT assertions: the field len is set to the optimal bit length, the
|
||||
// array bl_count contains the frequencies for each bit length.
|
||||
// The length opt_len is updated; static_len is also updated if stree is
|
||||
// not null.
|
||||
internal void gen_bitlen(DeflateManager s)
|
||||
{
|
||||
short[] tree = dyn_tree;
|
||||
short[] stree = staticTree.treeCodes;
|
||||
int[] extra = staticTree.extraBits;
|
||||
int base_Renamed = staticTree.extraBase;
|
||||
int max_length = staticTree.maxLength;
|
||||
int h; // heap index
|
||||
int n, m; // iterate over the tree elements
|
||||
int bits; // bit length
|
||||
int xbits; // extra bits
|
||||
short f; // frequency
|
||||
int overflow = 0; // number of elements with bit length too large
|
||||
|
||||
for (bits = 0; bits <= InternalConstants.MAX_BITS; bits++)
|
||||
s.bl_count[bits] = 0;
|
||||
|
||||
// In a first pass, compute the optimal bit lengths (which may
|
||||
// overflow in the case of the bit length tree).
|
||||
tree[s.heap[s.heap_max] * 2 + 1] = 0; // root of the heap
|
||||
|
||||
for (h = s.heap_max + 1; h < HEAP_SIZE; h++)
|
||||
{
|
||||
n = s.heap[h];
|
||||
bits = tree[tree[n * 2 + 1] * 2 + 1] + 1;
|
||||
if (bits > max_length)
|
||||
{
|
||||
bits = max_length; overflow++;
|
||||
}
|
||||
tree[n * 2 + 1] = (short) bits;
|
||||
// We overwrite tree[n*2+1] which is no longer needed
|
||||
|
||||
if (n > max_code)
|
||||
continue; // not a leaf node
|
||||
|
||||
s.bl_count[bits]++;
|
||||
xbits = 0;
|
||||
if (n >= base_Renamed)
|
||||
xbits = extra[n - base_Renamed];
|
||||
f = tree[n * 2];
|
||||
s.opt_len += f * (bits + xbits);
|
||||
if (stree != null)
|
||||
s.static_len += f * (stree[n * 2 + 1] + xbits);
|
||||
}
|
||||
if (overflow == 0)
|
||||
return ;
|
||||
|
||||
// This happens for example on obj2 and pic of the Calgary corpus
|
||||
// Find the first bit length which could increase:
|
||||
do
|
||||
{
|
||||
bits = max_length - 1;
|
||||
while (s.bl_count[bits] == 0)
|
||||
bits--;
|
||||
s.bl_count[bits]--; // move one leaf down the tree
|
||||
s.bl_count[bits + 1] = (short) (s.bl_count[bits + 1] + 2); // move one overflow item as its brother
|
||||
s.bl_count[max_length]--;
|
||||
// The brother of the overflow item also moves one step up,
|
||||
// but this does not affect bl_count[max_length]
|
||||
overflow -= 2;
|
||||
}
|
||||
while (overflow > 0);
|
||||
|
||||
for (bits = max_length; bits != 0; bits--)
|
||||
{
|
||||
n = s.bl_count[bits];
|
||||
while (n != 0)
|
||||
{
|
||||
m = s.heap[--h];
|
||||
if (m > max_code)
|
||||
continue;
|
||||
if (tree[m * 2 + 1] != bits)
|
||||
{
|
||||
s.opt_len = (int) (s.opt_len + ((long) bits - (long) tree[m * 2 + 1]) * (long) tree[m * 2]);
|
||||
tree[m * 2 + 1] = (short) bits;
|
||||
}
|
||||
n--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct one Huffman tree and assigns the code bit strings and lengths.
|
||||
// Update the total bit length for the current block.
|
||||
// IN assertion: the field freq is set for all tree elements.
|
||||
// OUT assertions: the fields len and code are set to the optimal bit length
|
||||
// and corresponding code. The length opt_len is updated; static_len is
|
||||
// also updated if stree is not null. The field max_code is set.
|
||||
internal void build_tree(DeflateManager s)
|
||||
{
|
||||
short[] tree = dyn_tree;
|
||||
short[] stree = staticTree.treeCodes;
|
||||
int elems = staticTree.elems;
|
||||
int n, m; // iterate over heap elements
|
||||
int max_code = -1; // largest code with non zero frequency
|
||||
int node; // new node being created
|
||||
|
||||
// Construct the initial heap, with least frequent element in
|
||||
// heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
|
||||
// heap[0] is not used.
|
||||
s.heap_len = 0;
|
||||
s.heap_max = HEAP_SIZE;
|
||||
|
||||
for (n = 0; n < elems; n++)
|
||||
{
|
||||
if (tree[n * 2] != 0)
|
||||
{
|
||||
s.heap[++s.heap_len] = max_code = n;
|
||||
s.depth[n] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
tree[n * 2 + 1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// The pkzip format requires that at least one distance code exists,
|
||||
// and that at least one bit should be sent even if there is only one
|
||||
// possible code. So to avoid special checks later on we force at least
|
||||
// two codes of non zero frequency.
|
||||
while (s.heap_len < 2)
|
||||
{
|
||||
node = s.heap[++s.heap_len] = (max_code < 2?++max_code:0);
|
||||
tree[node * 2] = 1;
|
||||
s.depth[node] = 0;
|
||||
s.opt_len--;
|
||||
if (stree != null)
|
||||
s.static_len -= stree[node * 2 + 1];
|
||||
// node is 0 or 1 so it does not have extra bits
|
||||
}
|
||||
this.max_code = max_code;
|
||||
|
||||
// The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
|
||||
// establish sub-heaps of increasing lengths:
|
||||
|
||||
for (n = s.heap_len / 2; n >= 1; n--)
|
||||
s.pqdownheap(tree, n);
|
||||
|
||||
// Construct the Huffman tree by repeatedly combining the least two
|
||||
// frequent nodes.
|
||||
|
||||
node = elems; // next internal node of the tree
|
||||
do
|
||||
{
|
||||
// n = node of least frequency
|
||||
n = s.heap[1];
|
||||
s.heap[1] = s.heap[s.heap_len--];
|
||||
s.pqdownheap(tree, 1);
|
||||
m = s.heap[1]; // m = node of next least frequency
|
||||
|
||||
s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency
|
||||
s.heap[--s.heap_max] = m;
|
||||
|
||||
// Create a new node father of n and m
|
||||
tree[node * 2] = unchecked((short) (tree[n * 2] + tree[m * 2]));
|
||||
s.depth[node] = (sbyte) (System.Math.Max((byte) s.depth[n], (byte) s.depth[m]) + 1);
|
||||
tree[n * 2 + 1] = tree[m * 2 + 1] = (short) node;
|
||||
|
||||
// and insert the new node in the heap
|
||||
s.heap[1] = node++;
|
||||
s.pqdownheap(tree, 1);
|
||||
}
|
||||
while (s.heap_len >= 2);
|
||||
|
||||
s.heap[--s.heap_max] = s.heap[1];
|
||||
|
||||
// At this point, the fields freq and dad are set. We can now
|
||||
// generate the bit lengths.
|
||||
|
||||
gen_bitlen(s);
|
||||
|
||||
// The field len is now set, we can generate the bit codes
|
||||
gen_codes(tree, max_code, s.bl_count);
|
||||
}
|
||||
|
||||
// Generate the codes for a given tree and bit counts (which need not be
|
||||
// optimal).
|
||||
// IN assertion: the array bl_count contains the bit length statistics for
|
||||
// the given tree and the field len is set for all tree elements.
|
||||
// OUT assertion: the field code is set for all tree elements of non
|
||||
// zero code length.
|
||||
internal static void gen_codes(short[] tree, int max_code, short[] bl_count)
|
||||
{
|
||||
short[] next_code = new short[InternalConstants.MAX_BITS + 1]; // next code value for each bit length
|
||||
short code = 0; // running code value
|
||||
int bits; // bit index
|
||||
int n; // code index
|
||||
|
||||
// The distribution counts are first used to generate the code values
|
||||
// without bit reversal.
|
||||
for (bits = 1; bits <= InternalConstants.MAX_BITS; bits++)
|
||||
unchecked {
|
||||
next_code[bits] = code = (short) ((code + bl_count[bits - 1]) << 1);
|
||||
}
|
||||
|
||||
// Check that the bit counts in bl_count are consistent. The last code
|
||||
// must be all ones.
|
||||
//Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
|
||||
// "inconsistent bit counts");
|
||||
//Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
|
||||
|
||||
for (n = 0; n <= max_code; n++)
|
||||
{
|
||||
int len = tree[n * 2 + 1];
|
||||
if (len == 0)
|
||||
continue;
|
||||
// Now reverse the bits
|
||||
tree[n * 2] = unchecked((short) (bi_reverse(next_code[len]++, len)));
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse the first len bits of a code, using straightforward code (a faster
|
||||
// method would use a table)
|
||||
// IN assertion: 1 <= len <= 15
|
||||
internal static int bi_reverse(int code, int len)
|
||||
{
|
||||
int res = 0;
|
||||
do
|
||||
{
|
||||
res |= code & 1;
|
||||
code >>= 1; //SharedUtils.URShift(code, 1);
|
||||
res <<= 1;
|
||||
}
|
||||
while (--len > 0);
|
||||
return res >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,546 @@
|
|||
// Zlib.cs
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2009-2011 Dino Chiesa and Microsoft Corporation.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This code module is part of DotNetZip, a zipfile class library.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This code is licensed under the Microsoft Public License.
|
||||
// See the file License.txt for the license details.
|
||||
// More info on: http://dotnetzip.codeplex.com
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Last Saved: <2011-August-03 19:52:28>
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This module defines classes for ZLIB compression and
|
||||
// decompression. This code is derived from the jzlib implementation of
|
||||
// zlib, but significantly modified. The object model is not the same,
|
||||
// and many of the behaviors are new or different. Nonetheless, in
|
||||
// keeping with the license for jzlib, the copyright to that code is
|
||||
// included below.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// The following notice applies to jzlib:
|
||||
//
|
||||
// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in
|
||||
// the documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. The names of the authors may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
//
|
||||
// jzlib is based on zlib-1.1.3.
|
||||
//
|
||||
// The following notice applies to zlib:
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (C) 1995-2004 Jean-loup Gailly and Mark Adler
|
||||
//
|
||||
// The ZLIB software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
// Jean-loup Gailly jloup@gzip.org
|
||||
// Mark Adler madler@alumni.caltech.edu
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
using System;
|
||||
using Interop=System.Runtime.InteropServices;
|
||||
|
||||
namespace BestHTTP.Decompression.Zlib
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Describes how to flush the current deflate operation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The different FlushType values are useful when using a Deflate in a streaming application.
|
||||
/// </remarks>
|
||||
public enum FlushType
|
||||
{
|
||||
/// <summary>No flush at all.</summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>Closes the current block, but doesn't flush it to
|
||||
/// the output. Used internally only in hypothetical
|
||||
/// scenarios. This was supposed to be removed by Zlib, but it is
|
||||
/// still in use in some edge cases.
|
||||
/// </summary>
|
||||
Partial,
|
||||
|
||||
/// <summary>
|
||||
/// Use this during compression to specify that all pending output should be
|
||||
/// flushed to the output buffer and the output should be aligned on a byte
|
||||
/// boundary. You might use this in a streaming communication scenario, so that
|
||||
/// the decompressor can get all input data available so far. When using this
|
||||
/// with a ZlibCodec, <c>AvailableBytesIn</c> will be zero after the call if
|
||||
/// enough output space has been provided before the call. Flushing will
|
||||
/// degrade compression and so it should be used only when necessary.
|
||||
/// </summary>
|
||||
Sync,
|
||||
|
||||
/// <summary>
|
||||
/// Use this during compression to specify that all output should be flushed, as
|
||||
/// with <c>FlushType.Sync</c>, but also, the compression state should be reset
|
||||
/// so that decompression can restart from this point if previous compressed
|
||||
/// data has been damaged or if random access is desired. Using
|
||||
/// <c>FlushType.Full</c> too often can significantly degrade the compression.
|
||||
/// </summary>
|
||||
Full,
|
||||
|
||||
/// <summary>Signals the end of the compression/decompression stream.</summary>
|
||||
Finish,
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The compression level to be used when using a DeflateStream or ZlibStream with CompressionMode.Compress.
|
||||
/// </summary>
|
||||
public enum CompressionLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// None means that the data will be simply stored, with no change at all.
|
||||
/// If you are producing ZIPs for use on Mac OSX, be aware that archives produced with CompressionLevel.None
|
||||
/// cannot be opened with the default zip reader. Use a different CompressionLevel.
|
||||
/// </summary>
|
||||
None= 0,
|
||||
/// <summary>
|
||||
/// Same as None.
|
||||
/// </summary>
|
||||
Level0 = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The fastest but least effective compression.
|
||||
/// </summary>
|
||||
BestSpeed = 1,
|
||||
|
||||
/// <summary>
|
||||
/// A synonym for BestSpeed.
|
||||
/// </summary>
|
||||
Level1 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// A little slower, but better, than level 1.
|
||||
/// </summary>
|
||||
Level2 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// A little slower, but better, than level 2.
|
||||
/// </summary>
|
||||
Level3 = 3,
|
||||
|
||||
/// <summary>
|
||||
/// A little slower, but better, than level 3.
|
||||
/// </summary>
|
||||
Level4 = 4,
|
||||
|
||||
/// <summary>
|
||||
/// A little slower than level 4, but with better compression.
|
||||
/// </summary>
|
||||
Level5 = 5,
|
||||
|
||||
/// <summary>
|
||||
/// The default compression level, with a good balance of speed and compression efficiency.
|
||||
/// </summary>
|
||||
Default = 6,
|
||||
/// <summary>
|
||||
/// A synonym for Default.
|
||||
/// </summary>
|
||||
Level6 = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Pretty good compression!
|
||||
/// </summary>
|
||||
Level7 = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Better compression than Level7!
|
||||
/// </summary>
|
||||
Level8 = 8,
|
||||
|
||||
/// <summary>
|
||||
/// The "best" compression, where best means greatest reduction in size of the input data stream.
|
||||
/// This is also the slowest compression.
|
||||
/// </summary>
|
||||
BestCompression = 9,
|
||||
|
||||
/// <summary>
|
||||
/// A synonym for BestCompression.
|
||||
/// </summary>
|
||||
Level9 = 9,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes options for how the compression algorithm is executed. Different strategies
|
||||
/// work better on different sorts of data. The strategy parameter can affect the compression
|
||||
/// ratio and the speed of compression but not the correctness of the compresssion.
|
||||
/// </summary>
|
||||
public enum CompressionStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// The default strategy is probably the best for normal data.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Filtered</c> strategy is intended to be used most effectively with data produced by a
|
||||
/// filter or predictor. By this definition, filtered data consists mostly of small
|
||||
/// values with a somewhat random distribution. In this case, the compression algorithm
|
||||
/// is tuned to compress them better. The effect of <c>Filtered</c> is to force more Huffman
|
||||
/// coding and less string matching; it is a half-step between <c>Default</c> and <c>HuffmanOnly</c>.
|
||||
/// </summary>
|
||||
Filtered = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Using <c>HuffmanOnly</c> will force the compressor to do Huffman encoding only, with no
|
||||
/// string matching.
|
||||
/// </summary>
|
||||
HuffmanOnly = 2,
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An enum to specify the direction of transcoding - whether to compress or decompress.
|
||||
/// </summary>
|
||||
public enum CompressionMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to specify that the stream should compress the data.
|
||||
/// </summary>
|
||||
Compress= 0,
|
||||
/// <summary>
|
||||
/// Used to specify that the stream should decompress the data.
|
||||
/// </summary>
|
||||
Decompress = 1,
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A general purpose exception class for exceptions in the Zlib library.
|
||||
/// </summary>
|
||||
[Interop.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d0000E")]
|
||||
internal class ZlibException : System.Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// The ZlibException class captures exception information generated
|
||||
/// by the Zlib library.
|
||||
/// </summary>
|
||||
public ZlibException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This ctor collects a message attached to the exception.
|
||||
/// </summary>
|
||||
/// <param name="s">the message for the exception.</param>
|
||||
public ZlibException(System.String s)
|
||||
: base(s)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class SharedUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs an unsigned bitwise right shift with the specified number
|
||||
/// </summary>
|
||||
/// <param name="number">Number to operate on</param>
|
||||
/// <param name="bits">Ammount of bits to shift</param>
|
||||
/// <returns>The resulting number from the shift operation</returns>
|
||||
public static int URShift(int number, int bits)
|
||||
{
|
||||
return (int)((uint)number >> bits);
|
||||
}
|
||||
|
||||
#if NOT
|
||||
/// <summary>
|
||||
/// Performs an unsigned bitwise right shift with the specified number
|
||||
/// </summary>
|
||||
/// <param name="number">Number to operate on</param>
|
||||
/// <param name="bits">Ammount of bits to shift</param>
|
||||
/// <returns>The resulting number from the shift operation</returns>
|
||||
public static long URShift(long number, int bits)
|
||||
{
|
||||
return (long) ((UInt64)number >> bits);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Reads a number of characters from the current source TextReader and writes
|
||||
/// the data to the target array at the specified index.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="sourceTextReader">The source TextReader to read from</param>
|
||||
/// <param name="target">Contains the array of characteres read from the source TextReader.</param>
|
||||
/// <param name="start">The starting index of the target array.</param>
|
||||
/// <param name="count">The maximum number of characters to read from the source TextReader.</param>
|
||||
///
|
||||
/// <returns>
|
||||
/// The number of characters read. The number will be less than or equal to
|
||||
/// count depending on the data available in the source TextReader. Returns -1
|
||||
/// if the end of the stream is reached.
|
||||
/// </returns>
|
||||
public static System.Int32 ReadInput(System.IO.TextReader sourceTextReader, byte[] target, int start, int count)
|
||||
{
|
||||
// Returns 0 bytes if not enough space in target
|
||||
if (target.Length == 0) return 0;
|
||||
|
||||
char[] charArray = new char[target.Length];
|
||||
int bytesRead = sourceTextReader.Read(charArray, start, count);
|
||||
|
||||
// Returns -1 if EOF
|
||||
if (bytesRead == 0) return -1;
|
||||
|
||||
for (int index = start; index < start + bytesRead; index++)
|
||||
target[index] = (byte)charArray[index];
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
internal static byte[] ToByteArray(System.String sourceString)
|
||||
{
|
||||
return System.Text.UTF8Encoding.UTF8.GetBytes(sourceString);
|
||||
}
|
||||
|
||||
|
||||
internal static char[] ToCharArray(byte[] byteArray)
|
||||
{
|
||||
return System.Text.UTF8Encoding.UTF8.GetChars(byteArray);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class InternalConstants
|
||||
{
|
||||
internal static readonly int MAX_BITS = 15;
|
||||
internal static readonly int BL_CODES = 19;
|
||||
internal static readonly int D_CODES = 30;
|
||||
internal static readonly int LITERALS = 256;
|
||||
internal static readonly int LENGTH_CODES = 29;
|
||||
internal static readonly int L_CODES = (LITERALS + 1 + LENGTH_CODES);
|
||||
|
||||
// Bit length codes must not exceed MAX_BL_BITS bits
|
||||
internal static readonly int MAX_BL_BITS = 7;
|
||||
|
||||
// repeat previous bit length 3-6 times (2 bits of repeat count)
|
||||
internal static readonly int REP_3_6 = 16;
|
||||
|
||||
// repeat a zero length 3-10 times (3 bits of repeat count)
|
||||
internal static readonly int REPZ_3_10 = 17;
|
||||
|
||||
// repeat a zero length 11-138 times (7 bits of repeat count)
|
||||
internal static readonly int REPZ_11_138 = 18;
|
||||
|
||||
}
|
||||
|
||||
internal sealed class StaticTree
|
||||
{
|
||||
internal static readonly short[] lengthAndLiteralsTreeCodes = new short[] {
|
||||
12, 8, 140, 8, 76, 8, 204, 8, 44, 8, 172, 8, 108, 8, 236, 8,
|
||||
28, 8, 156, 8, 92, 8, 220, 8, 60, 8, 188, 8, 124, 8, 252, 8,
|
||||
2, 8, 130, 8, 66, 8, 194, 8, 34, 8, 162, 8, 98, 8, 226, 8,
|
||||
18, 8, 146, 8, 82, 8, 210, 8, 50, 8, 178, 8, 114, 8, 242, 8,
|
||||
10, 8, 138, 8, 74, 8, 202, 8, 42, 8, 170, 8, 106, 8, 234, 8,
|
||||
26, 8, 154, 8, 90, 8, 218, 8, 58, 8, 186, 8, 122, 8, 250, 8,
|
||||
6, 8, 134, 8, 70, 8, 198, 8, 38, 8, 166, 8, 102, 8, 230, 8,
|
||||
22, 8, 150, 8, 86, 8, 214, 8, 54, 8, 182, 8, 118, 8, 246, 8,
|
||||
14, 8, 142, 8, 78, 8, 206, 8, 46, 8, 174, 8, 110, 8, 238, 8,
|
||||
30, 8, 158, 8, 94, 8, 222, 8, 62, 8, 190, 8, 126, 8, 254, 8,
|
||||
1, 8, 129, 8, 65, 8, 193, 8, 33, 8, 161, 8, 97, 8, 225, 8,
|
||||
17, 8, 145, 8, 81, 8, 209, 8, 49, 8, 177, 8, 113, 8, 241, 8,
|
||||
9, 8, 137, 8, 73, 8, 201, 8, 41, 8, 169, 8, 105, 8, 233, 8,
|
||||
25, 8, 153, 8, 89, 8, 217, 8, 57, 8, 185, 8, 121, 8, 249, 8,
|
||||
5, 8, 133, 8, 69, 8, 197, 8, 37, 8, 165, 8, 101, 8, 229, 8,
|
||||
21, 8, 149, 8, 85, 8, 213, 8, 53, 8, 181, 8, 117, 8, 245, 8,
|
||||
13, 8, 141, 8, 77, 8, 205, 8, 45, 8, 173, 8, 109, 8, 237, 8,
|
||||
29, 8, 157, 8, 93, 8, 221, 8, 61, 8, 189, 8, 125, 8, 253, 8,
|
||||
19, 9, 275, 9, 147, 9, 403, 9, 83, 9, 339, 9, 211, 9, 467, 9,
|
||||
51, 9, 307, 9, 179, 9, 435, 9, 115, 9, 371, 9, 243, 9, 499, 9,
|
||||
11, 9, 267, 9, 139, 9, 395, 9, 75, 9, 331, 9, 203, 9, 459, 9,
|
||||
43, 9, 299, 9, 171, 9, 427, 9, 107, 9, 363, 9, 235, 9, 491, 9,
|
||||
27, 9, 283, 9, 155, 9, 411, 9, 91, 9, 347, 9, 219, 9, 475, 9,
|
||||
59, 9, 315, 9, 187, 9, 443, 9, 123, 9, 379, 9, 251, 9, 507, 9,
|
||||
7, 9, 263, 9, 135, 9, 391, 9, 71, 9, 327, 9, 199, 9, 455, 9,
|
||||
39, 9, 295, 9, 167, 9, 423, 9, 103, 9, 359, 9, 231, 9, 487, 9,
|
||||
23, 9, 279, 9, 151, 9, 407, 9, 87, 9, 343, 9, 215, 9, 471, 9,
|
||||
55, 9, 311, 9, 183, 9, 439, 9, 119, 9, 375, 9, 247, 9, 503, 9,
|
||||
15, 9, 271, 9, 143, 9, 399, 9, 79, 9, 335, 9, 207, 9, 463, 9,
|
||||
47, 9, 303, 9, 175, 9, 431, 9, 111, 9, 367, 9, 239, 9, 495, 9,
|
||||
31, 9, 287, 9, 159, 9, 415, 9, 95, 9, 351, 9, 223, 9, 479, 9,
|
||||
63, 9, 319, 9, 191, 9, 447, 9, 127, 9, 383, 9, 255, 9, 511, 9,
|
||||
0, 7, 64, 7, 32, 7, 96, 7, 16, 7, 80, 7, 48, 7, 112, 7,
|
||||
8, 7, 72, 7, 40, 7, 104, 7, 24, 7, 88, 7, 56, 7, 120, 7,
|
||||
4, 7, 68, 7, 36, 7, 100, 7, 20, 7, 84, 7, 52, 7, 116, 7,
|
||||
3, 8, 131, 8, 67, 8, 195, 8, 35, 8, 163, 8, 99, 8, 227, 8
|
||||
};
|
||||
|
||||
internal static readonly short[] distTreeCodes = new short[] {
|
||||
0, 5, 16, 5, 8, 5, 24, 5, 4, 5, 20, 5, 12, 5, 28, 5,
|
||||
2, 5, 18, 5, 10, 5, 26, 5, 6, 5, 22, 5, 14, 5, 30, 5,
|
||||
1, 5, 17, 5, 9, 5, 25, 5, 5, 5, 21, 5, 13, 5, 29, 5,
|
||||
3, 5, 19, 5, 11, 5, 27, 5, 7, 5, 23, 5 };
|
||||
|
||||
internal static readonly StaticTree Literals;
|
||||
internal static readonly StaticTree Distances;
|
||||
internal static readonly StaticTree BitLengths;
|
||||
|
||||
internal short[] treeCodes; // static tree or null
|
||||
internal int[] extraBits; // extra bits for each code or null
|
||||
internal int extraBase; // base index for extra_bits
|
||||
internal int elems; // max number of elements in the tree
|
||||
internal int maxLength; // max bit length for the codes
|
||||
|
||||
private StaticTree(short[] treeCodes, int[] extraBits, int extraBase, int elems, int maxLength)
|
||||
{
|
||||
this.treeCodes = treeCodes;
|
||||
this.extraBits = extraBits;
|
||||
this.extraBase = extraBase;
|
||||
this.elems = elems;
|
||||
this.maxLength = maxLength;
|
||||
}
|
||||
static StaticTree()
|
||||
{
|
||||
Literals = new StaticTree(lengthAndLiteralsTreeCodes, ZTree.ExtraLengthBits, InternalConstants.LITERALS + 1, InternalConstants.L_CODES, InternalConstants.MAX_BITS);
|
||||
Distances = new StaticTree(distTreeCodes, ZTree.ExtraDistanceBits, 0, InternalConstants.D_CODES, InternalConstants.MAX_BITS);
|
||||
BitLengths = new StaticTree(null, ZTree.extra_blbits, 0, InternalConstants.BL_CODES, InternalConstants.MAX_BL_BITS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Computes an Adler-32 checksum.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Adler checksum is similar to a CRC checksum, but faster to compute, though less
|
||||
/// reliable. It is used in producing RFC1950 compressed streams. The Adler checksum
|
||||
/// is a required part of the "ZLIB" standard. Applications will almost never need to
|
||||
/// use this class directly.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <exclude/>
|
||||
public sealed class Adler
|
||||
{
|
||||
// largest prime smaller than 65536
|
||||
private static readonly uint BASE = 65521;
|
||||
// NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
|
||||
private static readonly int NMAX = 5552;
|
||||
|
||||
|
||||
#pragma warning disable 3001
|
||||
#pragma warning disable 3002
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the Adler32 checksum.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is used within ZLIB. You probably don't need to use this directly.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// To compute an Adler32 checksum on a byte array:
|
||||
/// <code>
|
||||
/// var adler = Adler.Adler32(0, null, 0, 0);
|
||||
/// adler = Adler.Adler32(adler, buffer, index, length);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static uint Adler32(uint adler, byte[] buf, int index, int len)
|
||||
{
|
||||
if (buf == null)
|
||||
return 1;
|
||||
|
||||
uint s1 = (uint) (adler & 0xffff);
|
||||
uint s2 = (uint) ((adler >> 16) & 0xffff);
|
||||
|
||||
while (len > 0)
|
||||
{
|
||||
int k = len < NMAX ? len : NMAX;
|
||||
len -= k;
|
||||
while (k >= 16)
|
||||
{
|
||||
//s1 += (buf[index++] & 0xff); s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
s1 += buf[index++]; s2 += s1;
|
||||
k -= 16;
|
||||
}
|
||||
if (k != 0)
|
||||
{
|
||||
do
|
||||
{
|
||||
s1 += buf[index++];
|
||||
s2 += s1;
|
||||
}
|
||||
while (--k != 0);
|
||||
}
|
||||
s1 %= BASE;
|
||||
s2 %= BASE;
|
||||
}
|
||||
return (uint)((s2 << 16) | s1);
|
||||
}
|
||||
#pragma warning restore 3001
|
||||
#pragma warning restore 3002
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,607 @@
|
|||
// ZlibBaseStream.cs
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This code module is part of DotNetZip, a zipfile class library.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This code is licensed under the Microsoft Public License.
|
||||
// See the file License.txt for the license details.
|
||||
// More info on: http://dotnetzip.codeplex.com
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// last saved (in emacs):
|
||||
// Time-stamp: <2011-August-06 21:22:38>
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This module defines the ZlibBaseStream class, which is an intnernal
|
||||
// base class for DeflateStream, ZlibStream and GZipStream.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace BestHTTP.Decompression.Zlib
|
||||
{
|
||||
|
||||
internal enum ZlibStreamFlavor { ZLIB = 1950, DEFLATE = 1951, GZIP = 1952 }
|
||||
|
||||
internal class ZlibBaseStream : System.IO.Stream
|
||||
{
|
||||
protected internal ZlibCodec _z = null; // deferred init... new ZlibCodec();
|
||||
|
||||
protected internal StreamMode _streamMode = StreamMode.Undefined;
|
||||
protected internal FlushType _flushMode;
|
||||
protected internal ZlibStreamFlavor _flavor;
|
||||
protected internal CompressionMode _compressionMode;
|
||||
protected internal CompressionLevel _level;
|
||||
protected internal bool _leaveOpen;
|
||||
protected internal byte[] _workingBuffer;
|
||||
protected internal int _bufferSize = ZlibConstants.WorkingBufferSizeDefault;
|
||||
protected internal int windowBitsMax;
|
||||
protected internal byte[] _buf1 = new byte[1];
|
||||
|
||||
protected internal System.IO.Stream _stream;
|
||||
protected internal CompressionStrategy Strategy = CompressionStrategy.Default;
|
||||
|
||||
// workitem 7159
|
||||
BestHTTP.Decompression.Crc.CRC32 crc;
|
||||
protected internal string _GzipFileName;
|
||||
protected internal string _GzipComment;
|
||||
protected internal DateTime _GzipMtime;
|
||||
protected internal int _gzipHeaderByteCount;
|
||||
|
||||
internal int Crc32 { get { if (crc == null) return 0; return crc.Crc32Result; } }
|
||||
|
||||
public ZlibBaseStream(System.IO.Stream stream,
|
||||
CompressionMode compressionMode,
|
||||
CompressionLevel level,
|
||||
ZlibStreamFlavor flavor,
|
||||
bool leaveOpen)
|
||||
:this(stream, compressionMode, level, flavor,leaveOpen, ZlibConstants.WindowBitsDefault)
|
||||
{ }
|
||||
|
||||
public ZlibBaseStream(System.IO.Stream stream,
|
||||
CompressionMode compressionMode,
|
||||
CompressionLevel level,
|
||||
ZlibStreamFlavor flavor,
|
||||
bool leaveOpen,
|
||||
int windowBits)
|
||||
: base()
|
||||
{
|
||||
this._flushMode = FlushType.None;
|
||||
//this._workingBuffer = new byte[WORKING_BUFFER_SIZE_DEFAULT];
|
||||
this._stream = stream;
|
||||
this._leaveOpen = leaveOpen;
|
||||
this._compressionMode = compressionMode;
|
||||
this._flavor = flavor;
|
||||
this._level = level;
|
||||
this.windowBitsMax = windowBits;
|
||||
// workitem 7159
|
||||
if (flavor == ZlibStreamFlavor.GZIP)
|
||||
{
|
||||
this.crc = new BestHTTP.Decompression.Crc.CRC32();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected internal bool _wantCompress
|
||||
{
|
||||
get
|
||||
{
|
||||
return (this._compressionMode == CompressionMode.Compress);
|
||||
}
|
||||
}
|
||||
|
||||
private ZlibCodec z
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_z == null)
|
||||
{
|
||||
bool wantRfc1950Header = (this._flavor == ZlibStreamFlavor.ZLIB);
|
||||
_z = new ZlibCodec();
|
||||
if (this._compressionMode == CompressionMode.Decompress)
|
||||
{
|
||||
_z.InitializeInflate(this.windowBitsMax, wantRfc1950Header);
|
||||
}
|
||||
else
|
||||
{
|
||||
_z.Strategy = Strategy;
|
||||
_z.InitializeDeflate(this._level, this.windowBitsMax, wantRfc1950Header);
|
||||
}
|
||||
}
|
||||
return _z;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private byte[] workingBuffer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_workingBuffer == null)
|
||||
_workingBuffer = BufferPool.Get(_bufferSize, true);
|
||||
return _workingBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override void Write(System.Byte[] buffer, int offset, int count)
|
||||
{
|
||||
// workitem 7159
|
||||
// calculate the CRC on the unccompressed data (before writing)
|
||||
if (crc != null)
|
||||
crc.SlurpBlock(buffer, offset, count);
|
||||
|
||||
if (_streamMode == StreamMode.Undefined)
|
||||
_streamMode = StreamMode.Writer;
|
||||
else if (_streamMode != StreamMode.Writer)
|
||||
throw new ZlibException("Cannot Write after Reading.");
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
// first reference of z property will initialize the private var _z
|
||||
z.InputBuffer = buffer;
|
||||
_z.NextIn = offset;
|
||||
_z.AvailableBytesIn = count;
|
||||
bool done = false;
|
||||
do
|
||||
{
|
||||
_z.OutputBuffer = workingBuffer;
|
||||
_z.NextOut = 0;
|
||||
_z.AvailableBytesOut = _workingBuffer.Length;
|
||||
int rc = (_wantCompress)
|
||||
? _z.Deflate(_flushMode)
|
||||
: _z.Inflate(_flushMode);
|
||||
if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
|
||||
throw new ZlibException((_wantCompress ? "de" : "in") + "flating: " + _z.Message);
|
||||
|
||||
//if (_workingBuffer.Length - _z.AvailableBytesOut > 0)
|
||||
_stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut);
|
||||
|
||||
done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0;
|
||||
|
||||
// If GZIP and de-compress, we're done when 8 bytes remain.
|
||||
if (_flavor == ZlibStreamFlavor.GZIP && !_wantCompress)
|
||||
done = (_z.AvailableBytesIn == 8 && _z.AvailableBytesOut != 0);
|
||||
|
||||
}
|
||||
while (!done);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void finish()
|
||||
{
|
||||
if (_z == null) return;
|
||||
|
||||
if (_streamMode == StreamMode.Writer)
|
||||
{
|
||||
bool done = false;
|
||||
do
|
||||
{
|
||||
_z.OutputBuffer = workingBuffer;
|
||||
_z.NextOut = 0;
|
||||
_z.AvailableBytesOut = _workingBuffer.Length;
|
||||
int rc = (_wantCompress)
|
||||
? _z.Deflate(FlushType.Finish)
|
||||
: _z.Inflate(FlushType.Finish);
|
||||
|
||||
if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK)
|
||||
{
|
||||
string verb = (_wantCompress ? "de" : "in") + "flating";
|
||||
if (_z.Message == null)
|
||||
throw new ZlibException(String.Format("{0}: (rc = {1})", verb, rc));
|
||||
else
|
||||
throw new ZlibException(verb + ": " + _z.Message);
|
||||
}
|
||||
|
||||
if (_workingBuffer.Length - _z.AvailableBytesOut > 0)
|
||||
{
|
||||
_stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut);
|
||||
}
|
||||
|
||||
done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0;
|
||||
// If GZIP and de-compress, we're done when 8 bytes remain.
|
||||
if (_flavor == ZlibStreamFlavor.GZIP && !_wantCompress)
|
||||
done = (_z.AvailableBytesIn == 8 && _z.AvailableBytesOut != 0);
|
||||
|
||||
}
|
||||
while (!done);
|
||||
|
||||
Flush();
|
||||
|
||||
// workitem 7159
|
||||
if (_flavor == ZlibStreamFlavor.GZIP)
|
||||
{
|
||||
if (_wantCompress)
|
||||
{
|
||||
// Emit the GZIP trailer: CRC32 and size mod 2^32
|
||||
int c1 = crc.Crc32Result;
|
||||
_stream.Write(BitConverter.GetBytes(c1), 0, 4);
|
||||
int c2 = (Int32)(crc.TotalBytesRead & 0x00000000FFFFFFFF);
|
||||
_stream.Write(BitConverter.GetBytes(c2), 0, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ZlibException("Writing with decompression is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
// workitem 7159
|
||||
else if (_streamMode == StreamMode.Reader)
|
||||
{
|
||||
if (_flavor == ZlibStreamFlavor.GZIP)
|
||||
{
|
||||
if (!_wantCompress)
|
||||
{
|
||||
// workitem 8501: handle edge case (decompress empty stream)
|
||||
if (_z.TotalBytesOut == 0L)
|
||||
return;
|
||||
|
||||
// Read and potentially verify the GZIP trailer:
|
||||
// CRC32 and size mod 2^32
|
||||
byte[] trailer = BufferPool.Get(8, true);
|
||||
|
||||
// workitems 8679 & 12554
|
||||
if (_z.AvailableBytesIn < 8)
|
||||
{
|
||||
// Make sure we have read to the end of the stream
|
||||
Array.Copy(_z.InputBuffer, _z.NextIn, trailer, 0, _z.AvailableBytesIn);
|
||||
int bytesNeeded = 8 - _z.AvailableBytesIn;
|
||||
int bytesRead = _stream.Read(trailer,
|
||||
_z.AvailableBytesIn,
|
||||
bytesNeeded);
|
||||
if (bytesNeeded != bytesRead)
|
||||
{
|
||||
BufferPool.Release(trailer);
|
||||
throw new ZlibException(String.Format("Missing or incomplete GZIP trailer. Expected 8 bytes, got {0}.",
|
||||
_z.AvailableBytesIn + bytesRead));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(_z.InputBuffer, _z.NextIn, trailer, 0, 8);
|
||||
}
|
||||
|
||||
Int32 crc32_expected = BitConverter.ToInt32(trailer, 0);
|
||||
Int32 crc32_actual = crc.Crc32Result;
|
||||
Int32 isize_expected = BitConverter.ToInt32(trailer, 4);
|
||||
Int32 isize_actual = (Int32)(_z.TotalBytesOut & 0x00000000FFFFFFFF);
|
||||
|
||||
if (crc32_actual != crc32_expected)
|
||||
{
|
||||
BufferPool.Release(trailer);
|
||||
throw new ZlibException(String.Format("Bad CRC32 in GZIP trailer. (actual({0:X8})!=expected({1:X8}))", crc32_actual, crc32_expected));
|
||||
}
|
||||
|
||||
if (isize_actual != isize_expected)
|
||||
{
|
||||
BufferPool.Release(trailer);
|
||||
throw new ZlibException(String.Format("Bad size in GZIP trailer. (actual({0})!=expected({1}))", isize_actual, isize_expected));
|
||||
}
|
||||
BufferPool.Release(trailer);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ZlibException("Reading with compression is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void end()
|
||||
{
|
||||
if (z == null)
|
||||
return;
|
||||
if (_wantCompress)
|
||||
{
|
||||
_z.EndDeflate();
|
||||
}
|
||||
else
|
||||
{
|
||||
_z.EndInflate();
|
||||
}
|
||||
_z = null;
|
||||
BufferPool.Release(_workingBuffer);
|
||||
_workingBuffer = null;
|
||||
}
|
||||
|
||||
|
||||
public
|
||||
#if !NETFX_CORE
|
||||
override
|
||||
#endif
|
||||
void Close()
|
||||
{
|
||||
if (_stream == null) return;
|
||||
try
|
||||
{
|
||||
finish();
|
||||
}
|
||||
finally
|
||||
{
|
||||
end();
|
||||
if (!_leaveOpen) _stream.Dispose();
|
||||
_stream = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
_stream.Flush();
|
||||
}
|
||||
|
||||
public override System.Int64 Seek(System.Int64 offset, System.IO.SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
//_outStream.Seek(offset, origin);
|
||||
}
|
||||
public override void SetLength(System.Int64 value)
|
||||
{
|
||||
_stream.SetLength(value);
|
||||
nomoreinput = false;
|
||||
}
|
||||
|
||||
|
||||
#if NOT
|
||||
public int Read()
|
||||
{
|
||||
if (Read(_buf1, 0, 1) == 0)
|
||||
return 0;
|
||||
// calculate CRC after reading
|
||||
if (crc!=null)
|
||||
crc.SlurpBlock(_buf1,0,1);
|
||||
return (_buf1[0] & 0xFF);
|
||||
}
|
||||
#endif
|
||||
|
||||
private bool nomoreinput = false;
|
||||
|
||||
|
||||
|
||||
private string ReadZeroTerminatedString()
|
||||
{
|
||||
var list = new System.Collections.Generic.List<byte>();
|
||||
bool done = false;
|
||||
do
|
||||
{
|
||||
// workitem 7740
|
||||
int n = _stream.Read(_buf1, 0, 1);
|
||||
if (n != 1)
|
||||
throw new ZlibException("Unexpected EOF reading GZIP header.");
|
||||
else
|
||||
{
|
||||
if (_buf1[0] == 0)
|
||||
done = true;
|
||||
else
|
||||
list.Add(_buf1[0]);
|
||||
}
|
||||
} while (!done);
|
||||
byte[] a = list.ToArray();
|
||||
return GZipStream.iso8859dash1.GetString(a, 0, a.Length);
|
||||
}
|
||||
|
||||
|
||||
private int _ReadAndValidateGzipHeader()
|
||||
{
|
||||
int totalBytesRead = 0;
|
||||
// read the header on the first read
|
||||
byte[] header = BufferPool.Get(10, true);
|
||||
int n = _stream.Read(header, 0, 10);
|
||||
|
||||
// workitem 8501: handle edge case (decompress empty stream)
|
||||
if (n == 0)
|
||||
{
|
||||
BufferPool.Release(header);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (n != 10)
|
||||
{
|
||||
BufferPool.Release(header);
|
||||
throw new ZlibException("Not a valid GZIP stream.");
|
||||
}
|
||||
|
||||
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
|
||||
{
|
||||
BufferPool.Release(header);
|
||||
throw new ZlibException("Bad GZIP header.");
|
||||
}
|
||||
|
||||
Int32 timet = BitConverter.ToInt32(header, 4);
|
||||
_GzipMtime = GZipStream._unixEpoch.AddSeconds(timet);
|
||||
totalBytesRead += n;
|
||||
if ((header[3] & 0x04) == 0x04)
|
||||
{
|
||||
// read and discard extra field
|
||||
n = _stream.Read(header, 0, 2); // 2-byte length field
|
||||
totalBytesRead += n;
|
||||
|
||||
Int16 extraLength = (Int16)(header[0] + header[1] * 256);
|
||||
byte[] extra = BufferPool.Get(extraLength, true);
|
||||
n = _stream.Read(extra, 0, extraLength);
|
||||
if (n != extraLength)
|
||||
{
|
||||
BufferPool.Release(extra);
|
||||
BufferPool.Release(header);
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
|
||||
}
|
||||
totalBytesRead += n;
|
||||
}
|
||||
if ((header[3] & 0x08) == 0x08)
|
||||
_GzipFileName = ReadZeroTerminatedString();
|
||||
if ((header[3] & 0x10) == 0x010)
|
||||
_GzipComment = ReadZeroTerminatedString();
|
||||
if ((header[3] & 0x02) == 0x02)
|
||||
Read(_buf1, 0, 1); // CRC16, ignore
|
||||
|
||||
BufferPool.Release(header);
|
||||
return totalBytesRead;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override System.Int32 Read(System.Byte[] buffer, System.Int32 offset, System.Int32 count)
|
||||
{
|
||||
// According to MS documentation, any implementation of the IO.Stream.Read function must:
|
||||
// (a) throw an exception if offset & count reference an invalid part of the buffer,
|
||||
// or if count < 0, or if buffer is null
|
||||
// (b) return 0 only upon EOF, or if count = 0
|
||||
// (c) if not EOF, then return at least 1 byte, up to <count> bytes
|
||||
|
||||
if (_streamMode == StreamMode.Undefined)
|
||||
{
|
||||
if (!this._stream.CanRead) throw new ZlibException("The stream is not readable.");
|
||||
// for the first read, set up some controls.
|
||||
_streamMode = StreamMode.Reader;
|
||||
// (The first reference to _z goes through the private accessor which
|
||||
// may initialize it.)
|
||||
z.AvailableBytesIn = 0;
|
||||
if (_flavor == ZlibStreamFlavor.GZIP)
|
||||
{
|
||||
_gzipHeaderByteCount = _ReadAndValidateGzipHeader();
|
||||
// workitem 8501: handle edge case (decompress empty stream)
|
||||
if (_gzipHeaderByteCount == 0)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (_streamMode != StreamMode.Reader)
|
||||
throw new ZlibException("Cannot Read after Writing.");
|
||||
|
||||
if (count == 0) return 0;
|
||||
if (nomoreinput && _wantCompress) return 0; // workitem 8557
|
||||
if (buffer == null) throw new ArgumentNullException("buffer");
|
||||
if (count < 0) throw new ArgumentOutOfRangeException("count");
|
||||
if (offset < buffer.GetLowerBound(0)) throw new ArgumentOutOfRangeException("offset");
|
||||
if ((offset + count) > buffer.GetLength(0)) throw new ArgumentOutOfRangeException("count");
|
||||
|
||||
int rc = 0;
|
||||
|
||||
// set up the output of the deflate/inflate codec:
|
||||
_z.OutputBuffer = buffer;
|
||||
_z.NextOut = offset;
|
||||
_z.AvailableBytesOut = count;
|
||||
|
||||
// This is necessary in case _workingBuffer has been resized. (new byte[])
|
||||
// (The first reference to _workingBuffer goes through the private accessor which
|
||||
// may initialize it.)
|
||||
_z.InputBuffer = workingBuffer;
|
||||
|
||||
do
|
||||
{
|
||||
// need data in _workingBuffer in order to deflate/inflate. Here, we check if we have any.
|
||||
if ((_z.AvailableBytesIn == 0) && (!nomoreinput))
|
||||
{
|
||||
// No data available, so try to Read data from the captive stream.
|
||||
_z.NextIn = 0;
|
||||
_z.AvailableBytesIn = _stream.Read(_workingBuffer, 0, _workingBuffer.Length);
|
||||
if (_z.AvailableBytesIn == 0)
|
||||
nomoreinput = true;
|
||||
|
||||
}
|
||||
// we have data in InputBuffer; now compress or decompress as appropriate
|
||||
rc = (_wantCompress)
|
||||
? _z.Deflate(_flushMode)
|
||||
: _z.Inflate(_flushMode);
|
||||
|
||||
if (nomoreinput && (rc == ZlibConstants.Z_BUF_ERROR))
|
||||
return 0;
|
||||
|
||||
if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
|
||||
throw new ZlibException(String.Format("{0}flating: rc={1} msg={2}", (_wantCompress ? "de" : "in"), rc, _z.Message));
|
||||
|
||||
if ((nomoreinput || rc == ZlibConstants.Z_STREAM_END) && (_z.AvailableBytesOut == count))
|
||||
break; // nothing more to read
|
||||
}
|
||||
//while (_z.AvailableBytesOut == count && rc == ZlibConstants.Z_OK);
|
||||
while (_z.AvailableBytesOut > 0 && !nomoreinput && rc == ZlibConstants.Z_OK);
|
||||
|
||||
|
||||
// workitem 8557
|
||||
// is there more room in output?
|
||||
if (_z.AvailableBytesOut > 0)
|
||||
{
|
||||
if (rc == ZlibConstants.Z_OK && _z.AvailableBytesIn == 0)
|
||||
{
|
||||
// deferred
|
||||
}
|
||||
|
||||
// are we completely done reading?
|
||||
if (nomoreinput)
|
||||
{
|
||||
// and in compression?
|
||||
if (_wantCompress)
|
||||
{
|
||||
// no more input data available; therefore we flush to
|
||||
// try to complete the read
|
||||
rc = _z.Deflate(FlushType.Finish);
|
||||
|
||||
if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
|
||||
throw new ZlibException(String.Format("Deflating: rc={0} msg={1}", rc, _z.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
rc = (count - _z.AvailableBytesOut);
|
||||
|
||||
// calculate CRC after reading
|
||||
if (crc != null)
|
||||
crc.SlurpBlock(buffer, offset, rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override System.Boolean CanRead
|
||||
{
|
||||
get { return this._stream.CanRead; }
|
||||
}
|
||||
|
||||
public override System.Boolean CanSeek
|
||||
{
|
||||
get { return this._stream.CanSeek; }
|
||||
}
|
||||
|
||||
public override System.Boolean CanWrite
|
||||
{
|
||||
get { return this._stream.CanWrite; }
|
||||
}
|
||||
|
||||
public override System.Int64 Length
|
||||
{
|
||||
get { return _stream.Length; }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
set { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
internal enum StreamMode
|
||||
{
|
||||
Writer,
|
||||
Reader,
|
||||
Undefined,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,712 @@
|
|||
// ZlibCodec.cs
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This code module is part of DotNetZip, a zipfile class library.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This code is licensed under the Microsoft Public License.
|
||||
// See the file License.txt for the license details.
|
||||
// More info on: http://dotnetzip.codeplex.com
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// last saved (in emacs):
|
||||
// Time-stamp: <2009-November-03 15:40:51>
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This module defines a Codec for ZLIB compression and
|
||||
// decompression. This code extends code that was based the jzlib
|
||||
// implementation of zlib, but this code is completely novel. The codec
|
||||
// class is new, and encapsulates some behaviors that are new, and some
|
||||
// that were present in other classes in the jzlib code base. In
|
||||
// keeping with the license for jzlib, the copyright to the jzlib code
|
||||
// is included below.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in
|
||||
// the documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. The names of the authors may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
//
|
||||
// This program is based on zlib-1.1.3; credit to authors
|
||||
// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
|
||||
// and contributors of zlib.
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
|
||||
using System;
|
||||
using Interop=System.Runtime.InteropServices;
|
||||
|
||||
namespace BestHTTP.Decompression.Zlib
|
||||
{
|
||||
/// <summary>
|
||||
/// Encoder and Decoder for ZLIB and DEFLATE (IETF RFC1950 and RFC1951).
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// This class compresses and decompresses data according to the Deflate algorithm
|
||||
/// and optionally, the ZLIB format, as documented in <see
|
||||
/// href="http://www.ietf.org/rfc/rfc1950.txt">RFC 1950 - ZLIB</see> and <see
|
||||
/// href="http://www.ietf.org/rfc/rfc1951.txt">RFC 1951 - DEFLATE</see>.
|
||||
/// </remarks>
|
||||
sealed internal class ZlibCodec
|
||||
{
|
||||
/// <summary>
|
||||
/// The buffer from which data is taken.
|
||||
/// </summary>
|
||||
public byte[] InputBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// An index into the InputBuffer array, indicating where to start reading.
|
||||
/// </summary>
|
||||
public int NextIn;
|
||||
|
||||
/// <summary>
|
||||
/// The number of bytes available in the InputBuffer, starting at NextIn.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Generally you should set this to InputBuffer.Length before the first Inflate() or Deflate() call.
|
||||
/// The class will update this number as calls to Inflate/Deflate are made.
|
||||
/// </remarks>
|
||||
public int AvailableBytesIn;
|
||||
|
||||
/// <summary>
|
||||
/// Total number of bytes read so far, through all calls to Inflate()/Deflate().
|
||||
/// </summary>
|
||||
public long TotalBytesIn;
|
||||
|
||||
/// <summary>
|
||||
/// Buffer to store output data.
|
||||
/// </summary>
|
||||
public byte[] OutputBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// An index into the OutputBuffer array, indicating where to start writing.
|
||||
/// </summary>
|
||||
public int NextOut;
|
||||
|
||||
/// <summary>
|
||||
/// The number of bytes available in the OutputBuffer, starting at NextOut.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Generally you should set this to OutputBuffer.Length before the first Inflate() or Deflate() call.
|
||||
/// The class will update this number as calls to Inflate/Deflate are made.
|
||||
/// </remarks>
|
||||
public int AvailableBytesOut;
|
||||
|
||||
/// <summary>
|
||||
/// Total number of bytes written to the output so far, through all calls to Inflate()/Deflate().
|
||||
/// </summary>
|
||||
public long TotalBytesOut;
|
||||
|
||||
/// <summary>
|
||||
/// used for diagnostics, when something goes wrong!
|
||||
/// </summary>
|
||||
public System.String Message;
|
||||
|
||||
internal DeflateManager dstate;
|
||||
internal InflateManager istate;
|
||||
|
||||
internal uint _Adler32;
|
||||
|
||||
/// <summary>
|
||||
/// The compression level to use in this codec. Useful only in compression mode.
|
||||
/// </summary>
|
||||
public CompressionLevel CompressLevel = CompressionLevel.Default;
|
||||
|
||||
/// <summary>
|
||||
/// The number of Window Bits to use.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This gauges the size of the sliding window, and hence the
|
||||
/// compression effectiveness as well as memory consumption. It's best to just leave this
|
||||
/// setting alone if you don't know what it is. The maximum value is 15 bits, which implies
|
||||
/// a 32k window.
|
||||
/// </remarks>
|
||||
public int WindowBits = ZlibConstants.WindowBitsDefault;
|
||||
|
||||
/// <summary>
|
||||
/// The compression strategy to use.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only effective in compression. The theory offered by ZLIB is that different
|
||||
/// strategies could potentially produce significant differences in compression behavior
|
||||
/// for different data sets. Unfortunately I don't have any good recommendations for how
|
||||
/// to set it differently. When I tested changing the strategy I got minimally different
|
||||
/// compression performance. It's best to leave this property alone if you don't have a
|
||||
/// good feel for it. Or, you may want to produce a test harness that runs through the
|
||||
/// different strategy options and evaluates them on different file types. If you do that,
|
||||
/// let me know your results.
|
||||
/// </remarks>
|
||||
public CompressionStrategy Strategy = CompressionStrategy.Default;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The Adler32 checksum on the data transferred through the codec so far. You probably don't need to look at this.
|
||||
/// </summary>
|
||||
public int Adler32 { get { return (int)_Adler32; } }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a ZlibCodec.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you use this default constructor, you will later have to explicitly call
|
||||
/// InitializeInflate() or InitializeDeflate() before using the ZlibCodec to compress
|
||||
/// or decompress.
|
||||
/// </remarks>
|
||||
public ZlibCodec() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a ZlibCodec that either compresses or decompresses.
|
||||
/// </summary>
|
||||
/// <param name="mode">
|
||||
/// Indicates whether the codec should compress (deflate) or decompress (inflate).
|
||||
/// </param>
|
||||
public ZlibCodec(CompressionMode mode)
|
||||
{
|
||||
if (mode == CompressionMode.Compress)
|
||||
{
|
||||
int rc = InitializeDeflate();
|
||||
if (rc != ZlibConstants.Z_OK) throw new ZlibException("Cannot initialize for deflate.");
|
||||
}
|
||||
else if (mode == CompressionMode.Decompress)
|
||||
{
|
||||
int rc = InitializeInflate();
|
||||
if (rc != ZlibConstants.Z_OK) throw new ZlibException("Cannot initialize for inflate.");
|
||||
}
|
||||
else throw new ZlibException("Invalid ZlibStreamFlavor.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the inflation state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is not necessary to call this before using the ZlibCodec to inflate data;
|
||||
/// It is implicitly called when you call the constructor.
|
||||
/// </remarks>
|
||||
/// <returns>Z_OK if everything goes well.</returns>
|
||||
public int InitializeInflate()
|
||||
{
|
||||
return InitializeInflate(this.WindowBits);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the inflation state with an explicit flag to
|
||||
/// govern the handling of RFC1950 header bytes.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// By default, the ZLIB header defined in <see
|
||||
/// href="http://www.ietf.org/rfc/rfc1950.txt">RFC 1950</see> is expected. If
|
||||
/// you want to read a zlib stream you should specify true for
|
||||
/// expectRfc1950Header. If you have a deflate stream, you will want to specify
|
||||
/// false. It is only necessary to invoke this initializer explicitly if you
|
||||
/// want to specify false.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <param name="expectRfc1950Header">whether to expect an RFC1950 header byte
|
||||
/// pair when reading the stream of data to be inflated.</param>
|
||||
///
|
||||
/// <returns>Z_OK if everything goes well.</returns>
|
||||
public int InitializeInflate(bool expectRfc1950Header)
|
||||
{
|
||||
return InitializeInflate(this.WindowBits, expectRfc1950Header);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the ZlibCodec for inflation, with the specified number of window bits.
|
||||
/// </summary>
|
||||
/// <param name="windowBits">The number of window bits to use. If you need to ask what that is,
|
||||
/// then you shouldn't be calling this initializer.</param>
|
||||
/// <returns>Z_OK if all goes well.</returns>
|
||||
public int InitializeInflate(int windowBits)
|
||||
{
|
||||
this.WindowBits = windowBits;
|
||||
return InitializeInflate(windowBits, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the inflation state with an explicit flag to govern the handling of
|
||||
/// RFC1950 header bytes.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// If you want to read a zlib stream you should specify true for
|
||||
/// expectRfc1950Header. In this case, the library will expect to find a ZLIB
|
||||
/// header, as defined in <see href="http://www.ietf.org/rfc/rfc1950.txt">RFC
|
||||
/// 1950</see>, in the compressed stream. If you will be reading a DEFLATE or
|
||||
/// GZIP stream, which does not have such a header, you will want to specify
|
||||
/// false.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <param name="expectRfc1950Header">whether to expect an RFC1950 header byte pair when reading
|
||||
/// the stream of data to be inflated.</param>
|
||||
/// <param name="windowBits">The number of window bits to use. If you need to ask what that is,
|
||||
/// then you shouldn't be calling this initializer.</param>
|
||||
/// <returns>Z_OK if everything goes well.</returns>
|
||||
public int InitializeInflate(int windowBits, bool expectRfc1950Header)
|
||||
{
|
||||
this.WindowBits = windowBits;
|
||||
if (dstate != null) throw new ZlibException("You may not call InitializeInflate() after calling InitializeDeflate().");
|
||||
istate = new InflateManager(expectRfc1950Header);
|
||||
return istate.Initialize(this, windowBits);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inflate the data in the InputBuffer, placing the result in the OutputBuffer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You must have set InputBuffer and OutputBuffer, NextIn and NextOut, and AvailableBytesIn and
|
||||
/// AvailableBytesOut before calling this method.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// private void InflateBuffer()
|
||||
/// {
|
||||
/// int bufferSize = 1024;
|
||||
/// byte[] buffer = new byte[bufferSize];
|
||||
/// ZlibCodec decompressor = new ZlibCodec();
|
||||
///
|
||||
/// Console.WriteLine("\n============================================");
|
||||
/// Console.WriteLine("Size of Buffer to Inflate: {0} bytes.", CompressedBytes.Length);
|
||||
/// MemoryStream ms = new MemoryStream(DecompressedBytes);
|
||||
///
|
||||
/// int rc = decompressor.InitializeInflate();
|
||||
///
|
||||
/// decompressor.InputBuffer = CompressedBytes;
|
||||
/// decompressor.NextIn = 0;
|
||||
/// decompressor.AvailableBytesIn = CompressedBytes.Length;
|
||||
///
|
||||
/// decompressor.OutputBuffer = buffer;
|
||||
///
|
||||
/// // pass 1: inflate
|
||||
/// do
|
||||
/// {
|
||||
/// decompressor.NextOut = 0;
|
||||
/// decompressor.AvailableBytesOut = buffer.Length;
|
||||
/// rc = decompressor.Inflate(FlushType.None);
|
||||
///
|
||||
/// if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
|
||||
/// throw new Exception("inflating: " + decompressor.Message);
|
||||
///
|
||||
/// ms.Write(decompressor.OutputBuffer, 0, buffer.Length - decompressor.AvailableBytesOut);
|
||||
/// }
|
||||
/// while (decompressor.AvailableBytesIn > 0 || decompressor.AvailableBytesOut == 0);
|
||||
///
|
||||
/// // pass 2: finish and flush
|
||||
/// do
|
||||
/// {
|
||||
/// decompressor.NextOut = 0;
|
||||
/// decompressor.AvailableBytesOut = buffer.Length;
|
||||
/// rc = decompressor.Inflate(FlushType.Finish);
|
||||
///
|
||||
/// if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK)
|
||||
/// throw new Exception("inflating: " + decompressor.Message);
|
||||
///
|
||||
/// if (buffer.Length - decompressor.AvailableBytesOut > 0)
|
||||
/// ms.Write(buffer, 0, buffer.Length - decompressor.AvailableBytesOut);
|
||||
/// }
|
||||
/// while (decompressor.AvailableBytesIn > 0 || decompressor.AvailableBytesOut == 0);
|
||||
///
|
||||
/// decompressor.EndInflate();
|
||||
/// }
|
||||
///
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="flush">The flush to use when inflating.</param>
|
||||
/// <returns>Z_OK if everything goes well.</returns>
|
||||
public int Inflate(FlushType flush)
|
||||
{
|
||||
if (istate == null)
|
||||
throw new ZlibException("No Inflate State!");
|
||||
return istate.Inflate(flush);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ends an inflation session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this after successively calling Inflate(). This will cause all buffers to be flushed.
|
||||
/// After calling this you cannot call Inflate() without a intervening call to one of the
|
||||
/// InitializeInflate() overloads.
|
||||
/// </remarks>
|
||||
/// <returns>Z_OK if everything goes well.</returns>
|
||||
public int EndInflate()
|
||||
{
|
||||
if (istate == null)
|
||||
throw new ZlibException("No Inflate State!");
|
||||
int ret = istate.End();
|
||||
istate = null;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// I don't know what this does!
|
||||
/// </summary>
|
||||
/// <returns>Z_OK if everything goes well.</returns>
|
||||
public int SyncInflate()
|
||||
{
|
||||
if (istate == null)
|
||||
throw new ZlibException("No Inflate State!");
|
||||
return istate.Sync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the ZlibCodec for deflation operation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The codec will use the MAX window bits and the default level of compression.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// int bufferSize = 40000;
|
||||
/// byte[] CompressedBytes = new byte[bufferSize];
|
||||
/// byte[] DecompressedBytes = new byte[bufferSize];
|
||||
///
|
||||
/// ZlibCodec compressor = new ZlibCodec();
|
||||
///
|
||||
/// compressor.InitializeDeflate(CompressionLevel.Default);
|
||||
///
|
||||
/// compressor.InputBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(TextToCompress);
|
||||
/// compressor.NextIn = 0;
|
||||
/// compressor.AvailableBytesIn = compressor.InputBuffer.Length;
|
||||
///
|
||||
/// compressor.OutputBuffer = CompressedBytes;
|
||||
/// compressor.NextOut = 0;
|
||||
/// compressor.AvailableBytesOut = CompressedBytes.Length;
|
||||
///
|
||||
/// while (compressor.TotalBytesIn != TextToCompress.Length && compressor.TotalBytesOut < bufferSize)
|
||||
/// {
|
||||
/// compressor.Deflate(FlushType.None);
|
||||
/// }
|
||||
///
|
||||
/// while (true)
|
||||
/// {
|
||||
/// int rc= compressor.Deflate(FlushType.Finish);
|
||||
/// if (rc == ZlibConstants.Z_STREAM_END) break;
|
||||
/// }
|
||||
///
|
||||
/// compressor.EndDeflate();
|
||||
///
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <returns>Z_OK if all goes well. You generally don't need to check the return code.</returns>
|
||||
public int InitializeDeflate()
|
||||
{
|
||||
return _InternalInitializeDeflate(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The codec will use the maximum window bits (15) and the specified
|
||||
/// CompressionLevel. It will emit a ZLIB stream as it compresses.
|
||||
/// </remarks>
|
||||
/// <param name="level">The compression level for the codec.</param>
|
||||
/// <returns>Z_OK if all goes well.</returns>
|
||||
public int InitializeDeflate(CompressionLevel level)
|
||||
{
|
||||
this.CompressLevel = level;
|
||||
return _InternalInitializeDeflate(true);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel,
|
||||
/// and the explicit flag governing whether to emit an RFC1950 header byte pair.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The codec will use the maximum window bits (15) and the specified CompressionLevel.
|
||||
/// If you want to generate a zlib stream, you should specify true for
|
||||
/// wantRfc1950Header. In this case, the library will emit a ZLIB
|
||||
/// header, as defined in <see href="http://www.ietf.org/rfc/rfc1950.txt">RFC
|
||||
/// 1950</see>, in the compressed stream.
|
||||
/// </remarks>
|
||||
/// <param name="level">The compression level for the codec.</param>
|
||||
/// <param name="wantRfc1950Header">whether to emit an initial RFC1950 byte pair in the compressed stream.</param>
|
||||
/// <returns>Z_OK if all goes well.</returns>
|
||||
public int InitializeDeflate(CompressionLevel level, bool wantRfc1950Header)
|
||||
{
|
||||
this.CompressLevel = level;
|
||||
return _InternalInitializeDeflate(wantRfc1950Header);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel,
|
||||
/// and the specified number of window bits.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The codec will use the specified number of window bits and the specified CompressionLevel.
|
||||
/// </remarks>
|
||||
/// <param name="level">The compression level for the codec.</param>
|
||||
/// <param name="bits">the number of window bits to use. If you don't know what this means, don't use this method.</param>
|
||||
/// <returns>Z_OK if all goes well.</returns>
|
||||
public int InitializeDeflate(CompressionLevel level, int bits)
|
||||
{
|
||||
this.CompressLevel = level;
|
||||
this.WindowBits = bits;
|
||||
return _InternalInitializeDeflate(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the ZlibCodec for deflation operation, using the specified
|
||||
/// CompressionLevel, the specified number of window bits, and the explicit flag
|
||||
/// governing whether to emit an RFC1950 header byte pair.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="level">The compression level for the codec.</param>
|
||||
/// <param name="wantRfc1950Header">whether to emit an initial RFC1950 byte pair in the compressed stream.</param>
|
||||
/// <param name="bits">the number of window bits to use. If you don't know what this means, don't use this method.</param>
|
||||
/// <returns>Z_OK if all goes well.</returns>
|
||||
public int InitializeDeflate(CompressionLevel level, int bits, bool wantRfc1950Header)
|
||||
{
|
||||
this.CompressLevel = level;
|
||||
this.WindowBits = bits;
|
||||
return _InternalInitializeDeflate(wantRfc1950Header);
|
||||
}
|
||||
|
||||
private int _InternalInitializeDeflate(bool wantRfc1950Header)
|
||||
{
|
||||
if (istate != null) throw new ZlibException("You may not call InitializeDeflate() after calling InitializeInflate().");
|
||||
dstate = new DeflateManager();
|
||||
dstate.WantRfc1950HeaderBytes = wantRfc1950Header;
|
||||
|
||||
return dstate.Initialize(this, this.CompressLevel, this.WindowBits, this.Strategy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deflate one batch of data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You must have set InputBuffer and OutputBuffer before calling this method.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// private void DeflateBuffer(CompressionLevel level)
|
||||
/// {
|
||||
/// int bufferSize = 1024;
|
||||
/// byte[] buffer = new byte[bufferSize];
|
||||
/// ZlibCodec compressor = new ZlibCodec();
|
||||
///
|
||||
/// Console.WriteLine("\n============================================");
|
||||
/// Console.WriteLine("Size of Buffer to Deflate: {0} bytes.", UncompressedBytes.Length);
|
||||
/// MemoryStream ms = new MemoryStream();
|
||||
///
|
||||
/// int rc = compressor.InitializeDeflate(level);
|
||||
///
|
||||
/// compressor.InputBuffer = UncompressedBytes;
|
||||
/// compressor.NextIn = 0;
|
||||
/// compressor.AvailableBytesIn = UncompressedBytes.Length;
|
||||
///
|
||||
/// compressor.OutputBuffer = buffer;
|
||||
///
|
||||
/// // pass 1: deflate
|
||||
/// do
|
||||
/// {
|
||||
/// compressor.NextOut = 0;
|
||||
/// compressor.AvailableBytesOut = buffer.Length;
|
||||
/// rc = compressor.Deflate(FlushType.None);
|
||||
///
|
||||
/// if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
|
||||
/// throw new Exception("deflating: " + compressor.Message);
|
||||
///
|
||||
/// ms.Write(compressor.OutputBuffer, 0, buffer.Length - compressor.AvailableBytesOut);
|
||||
/// }
|
||||
/// while (compressor.AvailableBytesIn > 0 || compressor.AvailableBytesOut == 0);
|
||||
///
|
||||
/// // pass 2: finish and flush
|
||||
/// do
|
||||
/// {
|
||||
/// compressor.NextOut = 0;
|
||||
/// compressor.AvailableBytesOut = buffer.Length;
|
||||
/// rc = compressor.Deflate(FlushType.Finish);
|
||||
///
|
||||
/// if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK)
|
||||
/// throw new Exception("deflating: " + compressor.Message);
|
||||
///
|
||||
/// if (buffer.Length - compressor.AvailableBytesOut > 0)
|
||||
/// ms.Write(buffer, 0, buffer.Length - compressor.AvailableBytesOut);
|
||||
/// }
|
||||
/// while (compressor.AvailableBytesIn > 0 || compressor.AvailableBytesOut == 0);
|
||||
///
|
||||
/// compressor.EndDeflate();
|
||||
///
|
||||
/// ms.Seek(0, SeekOrigin.Begin);
|
||||
/// CompressedBytes = new byte[compressor.TotalBytesOut];
|
||||
/// ms.Read(CompressedBytes, 0, CompressedBytes.Length);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="flush">whether to flush all data as you deflate. Generally you will want to
|
||||
/// use Z_NO_FLUSH here, in a series of calls to Deflate(), and then call EndDeflate() to
|
||||
/// flush everything.
|
||||
/// </param>
|
||||
/// <returns>Z_OK if all goes well.</returns>
|
||||
public int Deflate(FlushType flush)
|
||||
{
|
||||
if (dstate == null)
|
||||
throw new ZlibException("No Deflate State!");
|
||||
return dstate.Deflate(flush);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End a deflation session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this after making a series of one or more calls to Deflate(). All buffers are flushed.
|
||||
/// </remarks>
|
||||
/// <returns>Z_OK if all goes well.</returns>
|
||||
public int EndDeflate()
|
||||
{
|
||||
if (dstate == null)
|
||||
throw new ZlibException("No Deflate State!");
|
||||
// Room for improvement: dinoch Tue, 03 Nov 2009 15:39 (test this)
|
||||
//int ret = dstate.End();
|
||||
dstate = null;
|
||||
return ZlibConstants.Z_OK; //ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset a codec for another deflation session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this to reset the deflation state. For example if a thread is deflating
|
||||
/// non-consecutive blocks, you can call Reset() after the Deflate(Sync) of the first
|
||||
/// block and before the next Deflate(None) of the second block.
|
||||
/// </remarks>
|
||||
/// <returns>Z_OK if all goes well.</returns>
|
||||
public void ResetDeflate()
|
||||
{
|
||||
if (dstate == null)
|
||||
throw new ZlibException("No Deflate State!");
|
||||
dstate.Reset();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set the CompressionStrategy and CompressionLevel for a deflation session.
|
||||
/// </summary>
|
||||
/// <param name="level">the level of compression to use.</param>
|
||||
/// <param name="strategy">the strategy to use for compression.</param>
|
||||
/// <returns>Z_OK if all goes well.</returns>
|
||||
public int SetDeflateParams(CompressionLevel level, CompressionStrategy strategy)
|
||||
{
|
||||
if (dstate == null)
|
||||
throw new ZlibException("No Deflate State!");
|
||||
return dstate.SetParams(level, strategy);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set the dictionary to be used for either Inflation or Deflation.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The dictionary bytes to use.</param>
|
||||
/// <returns>Z_OK if all goes well.</returns>
|
||||
public int SetDictionary(byte[] dictionary)
|
||||
{
|
||||
if (istate != null)
|
||||
return istate.SetDictionary(dictionary);
|
||||
|
||||
if (dstate != null)
|
||||
return dstate.SetDictionary(dictionary);
|
||||
|
||||
throw new ZlibException("No Inflate or Deflate state!");
|
||||
}
|
||||
|
||||
// Flush as much pending output as possible. All deflate() output goes
|
||||
// through this function so some applications may wish to modify it
|
||||
// to avoid allocating a large strm->next_out buffer and copying into it.
|
||||
// (See also read_buf()).
|
||||
internal void flush_pending()
|
||||
{
|
||||
int len = dstate.pendingCount;
|
||||
|
||||
if (len > AvailableBytesOut)
|
||||
len = AvailableBytesOut;
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
if (dstate.pending.Length <= dstate.nextPending ||
|
||||
OutputBuffer.Length <= NextOut ||
|
||||
dstate.pending.Length < (dstate.nextPending + len) ||
|
||||
OutputBuffer.Length < (NextOut + len))
|
||||
{
|
||||
throw new ZlibException(String.Format("Invalid State. (pending.Length={0}, pendingCount={1})",
|
||||
dstate.pending.Length, dstate.pendingCount));
|
||||
}
|
||||
|
||||
Array.Copy(dstate.pending, dstate.nextPending, OutputBuffer, NextOut, len);
|
||||
|
||||
NextOut += len;
|
||||
dstate.nextPending += len;
|
||||
TotalBytesOut += len;
|
||||
AvailableBytesOut -= len;
|
||||
dstate.pendingCount -= len;
|
||||
if (dstate.pendingCount == 0)
|
||||
{
|
||||
dstate.nextPending = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Read a new buffer from the current input stream, update the adler32
|
||||
// and total number of bytes read. All deflate() input goes through
|
||||
// this function so some applications may wish to modify it to avoid
|
||||
// allocating a large strm->next_in buffer and copying from it.
|
||||
// (See also flush_pending()).
|
||||
internal int read_buf(byte[] buf, int start, int size)
|
||||
{
|
||||
int len = AvailableBytesIn;
|
||||
|
||||
if (len > size)
|
||||
len = size;
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
AvailableBytesIn -= len;
|
||||
|
||||
if (dstate.WantRfc1950HeaderBytes)
|
||||
{
|
||||
_Adler32 = Adler.Adler32(_Adler32, InputBuffer, NextIn, len);
|
||||
}
|
||||
Array.Copy(InputBuffer, NextIn, buf, start, len);
|
||||
NextIn += len;
|
||||
TotalBytesIn += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
// ZlibConstants.cs
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This code module is part of DotNetZip, a zipfile class library.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This code is licensed under the Microsoft Public License.
|
||||
// See the file License.txt for the license details.
|
||||
// More info on: http://dotnetzip.codeplex.com
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// last saved (in emacs):
|
||||
// Time-stamp: <2009-November-03 18:50:19>
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// This module defines constants used by the zlib class library. This
|
||||
// code is derived from the jzlib implementation of zlib, but
|
||||
// significantly modified. In keeping with the license for jzlib, the
|
||||
// copyright to that code is included here.
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in
|
||||
// the documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. The names of the authors may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
//
|
||||
// This program is based on zlib-1.1.3; credit to authors
|
||||
// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
|
||||
// and contributors of zlib.
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
|
||||
using System;
|
||||
|
||||
namespace BestHTTP.Decompression.Zlib
|
||||
{
|
||||
/// <summary>
|
||||
/// A bunch of constants used in the Zlib interface.
|
||||
/// </summary>
|
||||
public static class ZlibConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum number of window bits for the Deflate algorithm.
|
||||
/// </summary>
|
||||
public const int WindowBitsMax = 15; // 32K LZ77 window
|
||||
|
||||
/// <summary>
|
||||
/// The default number of window bits for the Deflate algorithm.
|
||||
/// </summary>
|
||||
public const int WindowBitsDefault = WindowBitsMax;
|
||||
|
||||
/// <summary>
|
||||
/// indicates everything is A-OK
|
||||
/// </summary>
|
||||
public const int Z_OK = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the last operation reached the end of the stream.
|
||||
/// </summary>
|
||||
public const int Z_STREAM_END = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The operation ended in need of a dictionary.
|
||||
/// </summary>
|
||||
public const int Z_NEED_DICT = 2;
|
||||
|
||||
/// <summary>
|
||||
/// There was an error with the stream - not enough data, not open and readable, etc.
|
||||
/// </summary>
|
||||
public const int Z_STREAM_ERROR = -2;
|
||||
|
||||
/// <summary>
|
||||
/// There was an error with the data - not enough data, bad data, etc.
|
||||
/// </summary>
|
||||
public const int Z_DATA_ERROR = -3;
|
||||
|
||||
/// <summary>
|
||||
/// There was an error with the working buffer.
|
||||
/// </summary>
|
||||
public const int Z_BUF_ERROR = -5;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the working buffer used in the ZlibCodec class. Defaults to 8192 bytes.
|
||||
/// </summary>
|
||||
#if NETCF
|
||||
public const int WorkingBufferSizeDefault = 8192;
|
||||
#else
|
||||
public const int WorkingBufferSizeDefault = 16384;
|
||||
#endif
|
||||
/// <summary>
|
||||
/// The minimum size of the working buffer used in the ZlibCodec class. Currently it is 128 bytes.
|
||||
/// </summary>
|
||||
public const int WorkingBufferSizeMin = 1024;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,487 @@
|
|||
//
|
||||
// System.IO.MemoryStream.cs
|
||||
//
|
||||
// Authors: Marcin Szczepanski (marcins@zipworld.com.au)
|
||||
// Patrik Torstensson
|
||||
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
|
||||
//
|
||||
// (c) 2001,2002 Marcin Szczepanski, Patrik Torstensson
|
||||
// (c) 2003 Ximian, Inc. (http://www.ximian.com)
|
||||
// Copyright (C) 2004 Novell (http://www.novell.com)
|
||||
//
|
||||
|
||||
//
|
||||
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a modified MemoryStream class to use VariableSizedBufferPool
|
||||
/// </summary>
|
||||
public sealed class BufferPoolMemoryStream : System.IO.Stream
|
||||
{
|
||||
bool canWrite;
|
||||
bool allowGetBuffer;
|
||||
int capacity;
|
||||
int length;
|
||||
byte[] internalBuffer;
|
||||
int initialIndex;
|
||||
bool expandable;
|
||||
bool streamClosed;
|
||||
int position;
|
||||
int dirty_bytes;
|
||||
bool releaseInternalBuffer;
|
||||
|
||||
public BufferPoolMemoryStream() : this(0)
|
||||
{
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(int capacity)
|
||||
{
|
||||
if (capacity < 0)
|
||||
throw new ArgumentOutOfRangeException("capacity");
|
||||
|
||||
canWrite = true;
|
||||
|
||||
//internalBuffer = capacity > 0 ? BufferPool.Get(capacity, true) : BufferPool.NoData;
|
||||
//this.capacity = internalBuffer.Length;
|
||||
//
|
||||
//expandable = true;
|
||||
//allowGetBuffer = true;
|
||||
var buffer = capacity > 0 ? BufferPool.Get(capacity, true) : BufferPool.NoData;
|
||||
InternalConstructor(buffer, 0, buffer.Length, true, true, true, true);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
InternalConstructor(buffer, 0, buffer.Length, true, false, true, false);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer, bool writable)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
InternalConstructor(buffer, 0, buffer.Length, writable, false, true, false);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer, int index, int count)
|
||||
{
|
||||
InternalConstructor(buffer, index, count, true, false, true, false);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable)
|
||||
{
|
||||
InternalConstructor(buffer, index, count, writable, false, true, false);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible)
|
||||
{
|
||||
InternalConstructor(buffer, index, count, writable, publiclyVisible, true, false);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible, bool releaseBuffer)
|
||||
{
|
||||
InternalConstructor(buffer, index, count, writable, publiclyVisible, releaseBuffer, false);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible, bool releaseBuffer, bool canExpand)
|
||||
{
|
||||
InternalConstructor(buffer, index, count, writable, publiclyVisible, releaseBuffer, canExpand);
|
||||
}
|
||||
|
||||
void InternalConstructor(byte[] buffer, int index, int count, bool writable, bool publicallyVisible, bool releaseBuffer, bool canExpand)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
if (index < 0 || count < 0)
|
||||
throw new ArgumentOutOfRangeException("index or count is less than 0.");
|
||||
|
||||
if (buffer.Length - index < count)
|
||||
throw new ArgumentException("index+count",
|
||||
"The size of the buffer is less than index + count.");
|
||||
|
||||
canWrite = writable;
|
||||
|
||||
internalBuffer = buffer;
|
||||
capacity = count + index;
|
||||
//length = capacity;
|
||||
length = 0;
|
||||
position = index;
|
||||
initialIndex = index;
|
||||
|
||||
allowGetBuffer = publicallyVisible;
|
||||
releaseInternalBuffer = releaseBuffer;
|
||||
expandable = canExpand;
|
||||
}
|
||||
|
||||
void CheckIfClosedThrowDisposed()
|
||||
{
|
||||
if (streamClosed)
|
||||
throw new ObjectDisposedException("MemoryStream");
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return !streamClosed; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return !streamClosed; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return (!streamClosed && canWrite); }
|
||||
}
|
||||
|
||||
public int Capacity
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
return capacity - initialIndex;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
if (value == capacity)
|
||||
return; // LAMENESS: see MemoryStreamTest.ConstructorFive
|
||||
|
||||
if (!expandable)
|
||||
throw new NotSupportedException("Cannot expand this MemoryStream");
|
||||
|
||||
if (value < 0 || value < length)
|
||||
throw new ArgumentOutOfRangeException("value",
|
||||
"New capacity cannot be negative or less than the current capacity " + value + " " + capacity);
|
||||
|
||||
byte[] newBuffer = null;
|
||||
if (value != 0)
|
||||
{
|
||||
newBuffer = BufferPool.Get(value, true);
|
||||
Buffer.BlockCopy(internalBuffer, 0, newBuffer, 0, length);
|
||||
}
|
||||
|
||||
dirty_bytes = 0; // discard any dirty area beyond previous length
|
||||
BufferPool.Release(internalBuffer);
|
||||
internalBuffer = newBuffer; // It's null when capacity is set to 0
|
||||
capacity = internalBuffer != null ? internalBuffer.Length : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
// LAMESPEC: The spec says to throw an IOException if the
|
||||
// stream is closed and an ObjectDisposedException if
|
||||
// "methods were called after the stream was closed". What
|
||||
// is the difference?
|
||||
|
||||
CheckIfClosedThrowDisposed();
|
||||
|
||||
// This is ok for MemoryStreamTest.ConstructorFive
|
||||
return length - initialIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
return position - initialIndex;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
if (value < 0)
|
||||
throw new ArgumentOutOfRangeException("value",
|
||||
"Position cannot be negative");
|
||||
|
||||
if (value > Int32.MaxValue)
|
||||
throw new ArgumentOutOfRangeException("value",
|
||||
"Position must be non-negative and less than 2^31 - 1 - origin");
|
||||
|
||||
position = initialIndex + (int)value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
streamClosed = true;
|
||||
expandable = false;
|
||||
if (disposing && internalBuffer != null && this.releaseInternalBuffer)
|
||||
BufferPool.Release(internalBuffer);
|
||||
internalBuffer = null;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public byte[] GetBuffer()
|
||||
{
|
||||
if (!allowGetBuffer)
|
||||
throw new UnauthorizedAccessException();
|
||||
|
||||
return internalBuffer;
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
if (offset < 0 || count < 0)
|
||||
throw new ArgumentOutOfRangeException("offset or count less than zero.");
|
||||
|
||||
if (buffer.Length - offset < count)
|
||||
throw new ArgumentException("offset+count",
|
||||
"The size of the buffer is less than offset + count.");
|
||||
|
||||
if (position >= length || count == 0)
|
||||
return 0;
|
||||
|
||||
if (position > length - count)
|
||||
count = length - position;
|
||||
|
||||
Buffer.BlockCopy(internalBuffer, position, buffer, offset, count);
|
||||
position += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
if (position >= length)
|
||||
return -1;
|
||||
|
||||
return internalBuffer[position++];
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin loc)
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
|
||||
// It's funny that they don't throw this exception for < Int32.MinValue
|
||||
if (offset > (long)Int32.MaxValue)
|
||||
throw new ArgumentOutOfRangeException("Offset out of range. " + offset);
|
||||
|
||||
int refPoint;
|
||||
switch (loc)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
if (offset < 0)
|
||||
throw new IOException("Attempted to seek before start of MemoryStream.");
|
||||
refPoint = initialIndex;
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
refPoint = position;
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
refPoint = length;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("loc", "Invalid SeekOrigin");
|
||||
}
|
||||
|
||||
// LAMESPEC: My goodness, how may LAMESPECs are there in this
|
||||
// class! :) In the spec for the Position property it's stated
|
||||
// "The position must not be more than one byte beyond the end of the stream."
|
||||
// In the spec for seek it says "Seeking to any location beyond the length of the
|
||||
// stream is supported." That's a contradiction i'd say.
|
||||
// I guess seek can go anywhere but if you use position it may get moved back.
|
||||
|
||||
refPoint += (int)offset;
|
||||
if (refPoint < initialIndex)
|
||||
throw new IOException("Attempted to seek before start of MemoryStream.");
|
||||
|
||||
position = refPoint;
|
||||
return position;
|
||||
}
|
||||
|
||||
int CalculateNewCapacity(int minimum)
|
||||
{
|
||||
if (minimum < 256)
|
||||
minimum = 256; // See GetBufferTwo test
|
||||
|
||||
if (minimum < capacity * 2)
|
||||
minimum = capacity * 2;
|
||||
|
||||
if (!UnityEngine.Mathf.IsPowerOfTwo(minimum))
|
||||
minimum = UnityEngine.Mathf.NextPowerOfTwo(minimum);
|
||||
|
||||
return minimum;
|
||||
}
|
||||
|
||||
void Expand(int newSize)
|
||||
{
|
||||
// We don't need to take into account the dirty bytes when incrementing the
|
||||
// Capacity, as changing it will only preserve the valid clear region.
|
||||
if (newSize > capacity)
|
||||
Capacity = CalculateNewCapacity(newSize);
|
||||
else if (dirty_bytes > 0)
|
||||
{
|
||||
Array.Clear(internalBuffer, length, dirty_bytes);
|
||||
dirty_bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
if (!expandable && value > capacity)
|
||||
throw new NotSupportedException("Expanding this MemoryStream is not supported");
|
||||
|
||||
CheckIfClosedThrowDisposed();
|
||||
|
||||
if (!canWrite)
|
||||
{
|
||||
throw new NotSupportedException("Cannot write to this MemoryStream");
|
||||
}
|
||||
|
||||
// LAMESPEC: AGAIN! It says to throw this exception if value is
|
||||
// greater than "the maximum length of the MemoryStream". I haven't
|
||||
// seen anywhere mention what the maximum length of a MemoryStream is and
|
||||
// since we're this far this memory stream is expandable.
|
||||
if (value < 0 || (value + initialIndex) > (long)Int32.MaxValue)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
int newSize = (int)value + initialIndex;
|
||||
|
||||
if (newSize > length)
|
||||
Expand(newSize);
|
||||
else if (newSize < length) // Postpone the call to Array.Clear till expand time
|
||||
dirty_bytes += length - newSize;
|
||||
|
||||
length = newSize;
|
||||
if (position > length)
|
||||
position = length;
|
||||
}
|
||||
|
||||
public byte[] ToArray()
|
||||
{
|
||||
return ToArray(false);
|
||||
}
|
||||
|
||||
public byte[] ToArray(bool canBeLarger)
|
||||
{
|
||||
int l = length - initialIndex;
|
||||
byte[] outBuffer = null;
|
||||
|
||||
if (l > 0)
|
||||
{
|
||||
if (canBeLarger)
|
||||
outBuffer = BufferPool.Get(l, true);
|
||||
else
|
||||
outBuffer = new byte[l];
|
||||
}
|
||||
else
|
||||
{
|
||||
outBuffer = BufferPool.NoData;
|
||||
}
|
||||
|
||||
if (internalBuffer != null)
|
||||
Buffer.BlockCopy(internalBuffer, initialIndex, outBuffer, 0, l);
|
||||
return outBuffer;
|
||||
}
|
||||
|
||||
public BufferSegment ToBufferSegment()
|
||||
{
|
||||
int l = length - initialIndex;
|
||||
byte[] outBuffer = l > 0 ? BufferPool.Get(l, true) : BufferPool.NoData;
|
||||
|
||||
if (internalBuffer != null)
|
||||
Buffer.BlockCopy(internalBuffer, initialIndex, outBuffer, 0, l);
|
||||
|
||||
return new BufferSegment(outBuffer, 0, l);
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
|
||||
if (!canWrite)
|
||||
throw new NotSupportedException("Cannot write to this stream.");
|
||||
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
if (offset < 0 || count < 0)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
if (buffer.Length - offset < count)
|
||||
throw new ArgumentException("offset+count",
|
||||
"The size of the buffer is less than offset + count.");
|
||||
|
||||
// reordered to avoid possible integer overflow
|
||||
if (position > length - count)
|
||||
Expand(position + count);
|
||||
|
||||
Buffer.BlockCopy(buffer, offset, internalBuffer, position, count);
|
||||
position += count;
|
||||
if (position >= length)
|
||||
length = position;
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
if (!canWrite)
|
||||
throw new NotSupportedException("Cannot write to this stream.");
|
||||
|
||||
if (position >= length)
|
||||
{
|
||||
Expand(position + 1);
|
||||
length = position + 1;
|
||||
}
|
||||
|
||||
internalBuffer[position++] = value;
|
||||
}
|
||||
|
||||
public void WriteTo(Stream stream)
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException("stream");
|
||||
|
||||
stream.Write(internalBuffer, initialIndex, length - initialIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
public class BufferSegmentStream : Stream
|
||||
{
|
||||
public override bool CanRead { get { return false; } }
|
||||
|
||||
public override bool CanSeek { get { return false; } }
|
||||
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override long Length { get { return this._length; } }
|
||||
protected long _length;
|
||||
|
||||
public override long Position { get { return 0; } set { } }
|
||||
|
||||
protected List<BufferSegment> bufferList = new List<BufferSegment>();
|
||||
|
||||
private byte[] _tempByteArray = new byte[1];
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (Read(this._tempByteArray, 0, 1) == 0)
|
||||
return -1;
|
||||
|
||||
return this._tempByteArray[0];
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int sumReadCount = 0;
|
||||
|
||||
while (count > 0 && bufferList.Count > 0)
|
||||
{
|
||||
BufferSegment buff = this.bufferList[0];
|
||||
|
||||
int readCount = Math.Min(count, buff.Count);
|
||||
|
||||
Array.Copy(buff.Data, buff.Offset, buffer, offset, readCount);
|
||||
|
||||
sumReadCount += readCount;
|
||||
offset += readCount;
|
||||
count -= readCount;
|
||||
|
||||
this.bufferList[0] = buff = buff.Slice(buff.Offset + readCount);
|
||||
|
||||
if (buff.Count == 0)
|
||||
{
|
||||
this.bufferList.RemoveAt(0);
|
||||
BufferPool.Release(buff.Data);
|
||||
}
|
||||
}
|
||||
|
||||
this._length -= sumReadCount;
|
||||
|
||||
return sumReadCount;
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
Write(new BufferSegment(buffer, offset, count));
|
||||
}
|
||||
|
||||
public virtual void Write(BufferSegment bufferSegment)
|
||||
{
|
||||
this.bufferList.Add(bufferSegment);
|
||||
this._length += bufferSegment.Count;
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
for (int i = 0; i < this.bufferList.Count; ++i)
|
||||
BufferPool.Release(this.bufferList[i]);
|
||||
this.bufferList.Clear();
|
||||
this._length = 0;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
public sealed class CircularBuffer<T>
|
||||
{
|
||||
public int Capacity { get; private set; }
|
||||
public int Count { get; private set; }
|
||||
public int StartIdx { get { return this.startIdx; } }
|
||||
public int EndIdx { get { return this.endIdx; } }
|
||||
|
||||
public T this[int idx]
|
||||
{
|
||||
get
|
||||
{
|
||||
int realIdx = (this.startIdx + idx) % this.Capacity;
|
||||
|
||||
return this.buffer[realIdx];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
int realIdx = (this.startIdx + idx) % this.Capacity;
|
||||
|
||||
this.buffer[realIdx] = value;
|
||||
}
|
||||
}
|
||||
|
||||
private T[] buffer;
|
||||
private int startIdx;
|
||||
private int endIdx;
|
||||
|
||||
public CircularBuffer(int capacity)
|
||||
{
|
||||
this.Capacity = capacity;
|
||||
}
|
||||
|
||||
public void Add(T element)
|
||||
{
|
||||
if (this.buffer == null)
|
||||
this.buffer = new T[this.Capacity];
|
||||
|
||||
this.buffer[this.endIdx] = element;
|
||||
|
||||
this.endIdx = (this.endIdx + 1) % this.Capacity;
|
||||
if (this.endIdx == this.startIdx)
|
||||
this.startIdx = (this.startIdx + 1) % this.Capacity;
|
||||
|
||||
this.Count = Math.Min(this.Count + 1, this.Capacity);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.Count = this.startIdx = this.endIdx = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = PlatformSupport.Text.StringBuilderPool.Get(2);
|
||||
sb.Append("[");
|
||||
|
||||
int idx = this.startIdx;
|
||||
while (idx != this.endIdx)
|
||||
{
|
||||
sb.Append(this.buffer[idx].ToString());
|
||||
|
||||
idx = (idx + 1) % this.Capacity;
|
||||
if (idx != this.endIdx)
|
||||
sb.Append("; ");
|
||||
}
|
||||
sb.Append("]");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,519 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BestHTTP.PlatformSupport.Text;
|
||||
|
||||
#if NETFX_CORE
|
||||
using Windows.Security.Cryptography;
|
||||
using Windows.Security.Cryptography.Core;
|
||||
using Windows.Storage.Streams;
|
||||
#else
|
||||
using Cryptography = System.Security.Cryptography;
|
||||
using FileStream = System.IO.FileStream;
|
||||
#endif
|
||||
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
#region ASCII Encoding (These are required because Windows Phone doesn't supports the Encoding.ASCII class.)
|
||||
|
||||
/// <summary>
|
||||
/// On WP8 platform there are no ASCII encoding.
|
||||
/// </summary>
|
||||
public static string AsciiToString(this byte[] bytes)
|
||||
{
|
||||
StringBuilder sb = StringBuilderPool.Get(bytes.Length); //new StringBuilder(bytes.Length);
|
||||
foreach (byte b in bytes)
|
||||
sb.Append(b <= 0x7f ? (char)b : '?');
|
||||
return StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On WP8 platform there are no ASCII encoding.
|
||||
/// </summary>
|
||||
public static BufferSegment GetASCIIBytes(this string str)
|
||||
{
|
||||
byte[] result = BufferPool.Get(str.Length, true);
|
||||
for (int i = 0; i < str.Length; ++i)
|
||||
{
|
||||
char ch = str[i];
|
||||
result[i] = (byte)((ch < (char)0x80) ? ch : '?');
|
||||
}
|
||||
|
||||
return new BufferSegment(result, 0, str.Length);
|
||||
}
|
||||
|
||||
public static void SendAsASCII(this BinaryWriter stream, string str)
|
||||
{
|
||||
for (int i = 0; i < str.Length; ++i)
|
||||
{
|
||||
char ch = str[i];
|
||||
|
||||
stream.Write((byte)((ch < (char)0x80) ? ch : '?'));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FileSystem WriteLine function support
|
||||
|
||||
public static void WriteLine(this Stream fs)
|
||||
{
|
||||
fs.Write(HTTPRequest.EOL, 0, 2);
|
||||
}
|
||||
|
||||
public static void WriteLine(this Stream fs, string line)
|
||||
{
|
||||
var buff = line.GetASCIIBytes();
|
||||
fs.Write(buff.Data, buff.Offset, buff.Count);
|
||||
fs.WriteLine();
|
||||
BufferPool.Release(buff);
|
||||
}
|
||||
|
||||
public static void WriteLine(this Stream fs, string format, params object[] values)
|
||||
{
|
||||
var buff = string.Format(format, values).GetASCIIBytes();
|
||||
fs.Write(buff.Data, buff.Offset, buff.Count);
|
||||
fs.WriteLine();
|
||||
BufferPool.Release(buff);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Other Extensions
|
||||
|
||||
public static BufferSegment AsBuffer(this byte[] bytes)
|
||||
{
|
||||
return new BufferSegment(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
public static BufferSegment AsBuffer(this byte[] bytes, int length)
|
||||
{
|
||||
return new BufferSegment(bytes, 0, length);
|
||||
}
|
||||
|
||||
public static BufferSegment AsBuffer(this byte[] bytes, int offset, int length)
|
||||
{
|
||||
return new BufferSegment(bytes, offset, length);
|
||||
}
|
||||
|
||||
public static string GetRequestPathAndQueryURL(this Uri uri)
|
||||
{
|
||||
string requestPathAndQuery = uri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped);
|
||||
|
||||
// http://forum.unity3d.com/threads/best-http-released.200006/page-26#post-2723250
|
||||
if (string.IsNullOrEmpty(requestPathAndQuery))
|
||||
requestPathAndQuery = "/";
|
||||
|
||||
return requestPathAndQuery;
|
||||
}
|
||||
|
||||
public static string[] FindOption(this string str, string option)
|
||||
{
|
||||
//s-maxage=2678400, must-revalidate, max-age=0
|
||||
string[] options = str.ToLowerInvariant().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
option = option.ToLowerInvariant();
|
||||
|
||||
for (int i = 0; i < options.Length; ++i)
|
||||
if (options[i].Contains(option))
|
||||
return options[i].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string[] FindOption(this string[] options, string option)
|
||||
{
|
||||
for (int i = 0; i < options.Length; ++i)
|
||||
if (options[i].Contains(option))
|
||||
return options[i].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void WriteArray(this Stream stream, byte[] array)
|
||||
{
|
||||
stream.Write(array, 0, array.Length);
|
||||
}
|
||||
|
||||
public static void WriteBufferSegment(this Stream stream, BufferSegment buffer)
|
||||
{
|
||||
stream.Write(buffer.Data, buffer.Offset, buffer.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the Uri's host is a valid IPv4 or IPv6 address.
|
||||
/// </summary>
|
||||
public static bool IsHostIsAnIPAddress(this Uri uri)
|
||||
{
|
||||
if (uri == null)
|
||||
return false;
|
||||
|
||||
return IsIpV4AddressValid(uri.Host) || IsIpV6AddressValid(uri.Host);
|
||||
}
|
||||
|
||||
// Original idea from: https://www.code4copy.com/csharp/c-validate-ip-address-string/
|
||||
// Working regex: https://www.regular-expressions.info/ip.html
|
||||
private static readonly System.Text.RegularExpressions.Regex validIpV4AddressRegex = new System.Text.RegularExpressions.Regex("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Validates an IPv4 address.
|
||||
/// </summary>
|
||||
public static bool IsIpV4AddressValid(string address)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(address))
|
||||
return validIpV4AddressRegex.IsMatch(address.Trim());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates an IPv6 address.
|
||||
/// </summary>
|
||||
public static bool IsIpV6AddressValid(string address)
|
||||
{
|
||||
#if !NETFX_CORE
|
||||
if (!string.IsNullOrEmpty(address))
|
||||
{
|
||||
System.Net.IPAddress ip;
|
||||
if (System.Net.IPAddress.TryParse(address, out ip))
|
||||
return ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region String Conversions
|
||||
|
||||
public static int ToInt32(this string str, int defaultValue = default(int))
|
||||
{
|
||||
if (str == null)
|
||||
return defaultValue;
|
||||
|
||||
try
|
||||
{
|
||||
return int.Parse(str);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static long ToInt64(this string str, long defaultValue = default(long))
|
||||
{
|
||||
if (str == null)
|
||||
return defaultValue;
|
||||
|
||||
try
|
||||
{
|
||||
return long.Parse(str);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static DateTime ToDateTime(this string str, DateTime defaultValue = default(DateTime))
|
||||
{
|
||||
if (str == null)
|
||||
return defaultValue;
|
||||
|
||||
try
|
||||
{
|
||||
DateTime.TryParse(str, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out defaultValue);
|
||||
return defaultValue.ToUniversalTime();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToStrOrEmpty(this string str)
|
||||
{
|
||||
if (str == null)
|
||||
return String.Empty;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public static string ToStr(this string str, string defaultVale)
|
||||
{
|
||||
if (str == null)
|
||||
return defaultVale;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public static string ToBinaryStr(this byte value)
|
||||
{
|
||||
return Convert.ToString(value, 2).PadLeft(8, '0');
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MD5 Hashing
|
||||
|
||||
public static string CalculateMD5Hash(this string input)
|
||||
{
|
||||
var asciiBuff = input.GetASCIIBytes();
|
||||
var hash = asciiBuff.CalculateMD5Hash();
|
||||
BufferPool.Release(asciiBuff);
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static string CalculateMD5Hash(this BufferSegment input)
|
||||
{
|
||||
#if NETFX_CORE
|
||||
var alg = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Md5);
|
||||
//IBuffer buff = CryptographicBuffer.CreateFromByteArray(input);
|
||||
IBuffer buff = System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBufferExtensions.AsBuffer(input.Data, input.Offset, input.Count);
|
||||
var hashed = alg.HashData(buff);
|
||||
var res = CryptographicBuffer.EncodeToHexString(hashed);
|
||||
return res;
|
||||
#else
|
||||
using (var md5 = Cryptography.MD5.Create()) {
|
||||
var hash = md5.ComputeHash(input.Data, input.Offset, input.Count);
|
||||
var sb = StringBuilderPool.Get(hash.Length); //new StringBuilder(hash.Length);
|
||||
for (int i = 0; i < hash.Length; ++i)
|
||||
sb.Append(hash[i].ToString("x2"));
|
||||
BufferPool.Release(hash);
|
||||
return StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Efficient String Parsing Helpers
|
||||
|
||||
internal static string Read(this string str, ref int pos, char block, bool needResult = true)
|
||||
{
|
||||
return str.Read(ref pos, (ch) => ch != block, needResult);
|
||||
}
|
||||
|
||||
internal static string Read(this string str, ref int pos, Func<char, bool> block, bool needResult = true)
|
||||
{
|
||||
if (pos >= str.Length)
|
||||
return string.Empty;
|
||||
|
||||
str.SkipWhiteSpace(ref pos);
|
||||
|
||||
int startPos = pos;
|
||||
|
||||
while (pos < str.Length && block(str[pos]))
|
||||
pos++;
|
||||
|
||||
string result = needResult ? str.Substring(startPos, pos - startPos) : null;
|
||||
|
||||
// set position to the next char
|
||||
pos++;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static string ReadPossibleQuotedText(this string str, ref int pos)
|
||||
{
|
||||
string result = string.Empty;
|
||||
if (str == null)
|
||||
return result;
|
||||
|
||||
// It's a quoted text?
|
||||
if (str[pos] == '\"')
|
||||
{
|
||||
// Skip the starting quote
|
||||
str.Read(ref pos, '\"', false);
|
||||
|
||||
// Read the text until the ending quote
|
||||
result = str.Read(ref pos, '\"');
|
||||
|
||||
// Next option
|
||||
str.Read(ref pos, (ch) => ch != ',' && ch != ';', false);
|
||||
}
|
||||
else
|
||||
// It's not a quoted text, so we will read until the next option
|
||||
result = str.Read(ref pos, (ch) => ch != ',' && ch != ';');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static void SkipWhiteSpace(this string str, ref int pos)
|
||||
{
|
||||
if (pos >= str.Length)
|
||||
return;
|
||||
|
||||
while (pos < str.Length && char.IsWhiteSpace(str[pos]))
|
||||
pos++;
|
||||
}
|
||||
|
||||
internal static string TrimAndLower(this string str)
|
||||
{
|
||||
if (str == null)
|
||||
return null;
|
||||
|
||||
char[] buffer = new char[str.Length];
|
||||
int length = 0;
|
||||
|
||||
for (int i = 0; i < str.Length; ++i)
|
||||
{
|
||||
char ch = str[i];
|
||||
if (!char.IsWhiteSpace(ch) && !char.IsControl(ch))
|
||||
buffer[length++] = char.ToLowerInvariant(ch);
|
||||
}
|
||||
|
||||
return new string(buffer, 0, length);
|
||||
}
|
||||
|
||||
internal static char? Peek(this string str, int pos)
|
||||
{
|
||||
if (pos < 0 || pos >= str.Length)
|
||||
return null;
|
||||
|
||||
return str[pos];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Specialized String Parsers
|
||||
|
||||
//public, max-age=2592000
|
||||
internal static List<HeaderValue> ParseOptionalHeader(this string str)
|
||||
{
|
||||
List<HeaderValue> result = new List<HeaderValue>();
|
||||
|
||||
if (str == null)
|
||||
return result;
|
||||
|
||||
int idx = 0;
|
||||
|
||||
// process the rest of the text
|
||||
while (idx < str.Length)
|
||||
{
|
||||
// Read key
|
||||
string key = str.Read(ref idx, (ch) => ch != '=' && ch != ',').TrimAndLower();
|
||||
HeaderValue qp = new HeaderValue(key);
|
||||
|
||||
if (str[idx - 1] == '=')
|
||||
qp.Value = str.ReadPossibleQuotedText(ref idx);
|
||||
|
||||
result.Add(qp);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//deflate, gzip, x-gzip, identity, *;q=0
|
||||
internal static List<HeaderValue> ParseQualityParams(this string str)
|
||||
{
|
||||
List<HeaderValue> result = new List<HeaderValue>();
|
||||
|
||||
if (str == null)
|
||||
return result;
|
||||
|
||||
int idx = 0;
|
||||
while (idx < str.Length)
|
||||
{
|
||||
string key = str.Read(ref idx, (ch) => ch != ',' && ch != ';').TrimAndLower();
|
||||
|
||||
HeaderValue qp = new HeaderValue(key);
|
||||
|
||||
if (str[idx - 1] == ';')
|
||||
{
|
||||
str.Read(ref idx, '=', false);
|
||||
qp.Value = str.Read(ref idx, ',');
|
||||
}
|
||||
|
||||
result.Add(qp);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Buffer Filling
|
||||
|
||||
/// <summary>
|
||||
/// Will fill the entire buffer from the stream. Will throw an exception when the underlying stream is closed.
|
||||
/// </summary>
|
||||
public static void ReadBuffer(this Stream stream, byte[] buffer)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int read = stream.Read(buffer, count, buffer.Length - count);
|
||||
|
||||
if (read <= 0)
|
||||
throw ExceptionHelper.ServerClosedTCPStream();
|
||||
|
||||
count += read;
|
||||
} while (count < buffer.Length);
|
||||
}
|
||||
|
||||
public static void ReadBuffer(this Stream stream, byte[] buffer, int length)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int read = stream.Read(buffer, count, length - count);
|
||||
|
||||
if (read <= 0)
|
||||
throw ExceptionHelper.ServerClosedTCPStream();
|
||||
|
||||
count += read;
|
||||
} while (count < length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BufferPoolMemoryStream
|
||||
|
||||
public static void WriteString(this BufferPoolMemoryStream ms, string str)
|
||||
{
|
||||
var byteCount = Encoding.UTF8.GetByteCount(str);
|
||||
byte[] buffer = BufferPool.Get(byteCount, true);
|
||||
Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, 0);
|
||||
ms.Write(buffer, 0, byteCount);
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
public static void WriteLine(this BufferPoolMemoryStream ms)
|
||||
{
|
||||
ms.Write(HTTPRequest.EOL, 0, HTTPRequest.EOL.Length);
|
||||
}
|
||||
|
||||
public static void WriteLine(this BufferPoolMemoryStream ms, string str)
|
||||
{
|
||||
ms.WriteString(str);
|
||||
ms.Write(HTTPRequest.EOL, 0, HTTPRequest.EOL.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#if NET_STANDARD_2_0 || NETFX_CORE || NET_4_6
|
||||
public static void Clear<T>(this System.Collections.Concurrent.ConcurrentQueue<T> queue)
|
||||
{
|
||||
T result;
|
||||
while (queue.TryDequeue(out result))
|
||||
;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static class ExceptionHelper
|
||||
{
|
||||
public static Exception ServerClosedTCPStream()
|
||||
{
|
||||
return new Exception("TCP Stream closed unexpectedly by the remote server");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,409 @@
|
|||
// Based on https://github.com/nickgravelyn/UnityToolbag/blob/master/Future/Future.cs
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017, Nick Gravelyn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
* */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BestHTTP.Futures
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the state of a future.
|
||||
/// </summary>
|
||||
public enum FutureState
|
||||
{
|
||||
/// <summary>
|
||||
/// The future hasn't begun to resolve a value.
|
||||
/// </summary>
|
||||
Pending,
|
||||
|
||||
/// <summary>
|
||||
/// The future is working on resolving a value.
|
||||
/// </summary>
|
||||
Processing,
|
||||
|
||||
/// <summary>
|
||||
/// The future has a value ready.
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// The future failed to resolve a value.
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the interface of an object that can be used to track a future value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object being retrieved.</typeparam>
|
||||
public interface IFuture<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the state of the future.
|
||||
/// </summary>
|
||||
FutureState state { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value if the State is Success.
|
||||
/// </summary>
|
||||
T value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the failure exception if the State is Error.
|
||||
/// </summary>
|
||||
Exception error { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke when an intermediate result is known.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
IFuture<T> OnItem(FutureValueCallback<T> callback);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke if the future value is retrieved successfully.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
IFuture<T> OnSuccess(FutureValueCallback<T> callback);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke if the future has an error.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
IFuture<T> OnError(FutureErrorCallback callback);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke if the future value is retrieved successfully or has an error.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
IFuture<T> OnComplete(FutureCallback<T> callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the signature for callbacks used by the future.
|
||||
/// </summary>
|
||||
/// <param name="future">The future.</param>
|
||||
public delegate void FutureCallback<T>(IFuture<T> future);
|
||||
|
||||
public delegate void FutureValueCallback<T>(T value);
|
||||
public delegate void FutureErrorCallback(Exception error);
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of <see cref="IFuture{T}"/> that can be used internally by methods that return futures.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Methods should always return the <see cref="IFuture{T}"/> interface when calling code requests a future.
|
||||
/// This class is intended to be constructed internally in the method to provide a simple implementation of
|
||||
/// the interface. By returning the interface instead of the class it ensures the implementation can change
|
||||
/// later on if requirements change, without affecting the calling code.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of object being retrieved.</typeparam>
|
||||
public class Future<T> : IFuture<T>
|
||||
{
|
||||
private volatile FutureState _state;
|
||||
private T _value;
|
||||
private Exception _error;
|
||||
private Func<T> _processFunc;
|
||||
|
||||
private readonly List<FutureValueCallback<T>> _itemCallbacks = new List<FutureValueCallback<T>>();
|
||||
private readonly List<FutureValueCallback<T>> _successCallbacks = new List<FutureValueCallback<T>>();
|
||||
private readonly List<FutureErrorCallback> _errorCallbacks = new List<FutureErrorCallback>();
|
||||
private readonly List<FutureCallback<T>> _complationCallbacks = new List<FutureCallback<T>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the future.
|
||||
/// </summary>
|
||||
public FutureState state { get { return _state; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value if the State is Success.
|
||||
/// </summary>
|
||||
public T value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_state != FutureState.Success && _state != FutureState.Processing)
|
||||
{
|
||||
throw new InvalidOperationException("value is not available unless state is Success or Processing.");
|
||||
}
|
||||
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the failure exception if the State is Error.
|
||||
/// </summary>
|
||||
public Exception error
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_state != FutureState.Error)
|
||||
{
|
||||
throw new InvalidOperationException("error is not available unless state is Error.");
|
||||
}
|
||||
|
||||
return _error;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Future{T}"/> class.
|
||||
/// </summary>
|
||||
public Future()
|
||||
{
|
||||
_state = FutureState.Pending;
|
||||
}
|
||||
|
||||
public IFuture<T> OnItem(FutureValueCallback<T> callback)
|
||||
{
|
||||
if (_state < FutureState.Success && !_itemCallbacks.Contains(callback))
|
||||
_itemCallbacks.Add(callback);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke if the future value is retrieved successfully.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
public IFuture<T> OnSuccess(FutureValueCallback<T> callback)
|
||||
{
|
||||
if (_state == FutureState.Success)
|
||||
{
|
||||
callback(this.value);
|
||||
}
|
||||
else if (_state != FutureState.Error && !_successCallbacks.Contains(callback))
|
||||
{
|
||||
_successCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke if the future has an error.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
public IFuture<T> OnError(FutureErrorCallback callback)
|
||||
{
|
||||
if (_state == FutureState.Error)
|
||||
{
|
||||
callback(this.error);
|
||||
}
|
||||
else if (_state != FutureState.Success && !_errorCallbacks.Contains(callback))
|
||||
{
|
||||
_errorCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke if the future value is retrieved successfully or has an error.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
public IFuture<T> OnComplete(FutureCallback<T> callback)
|
||||
{
|
||||
if (_state == FutureState.Success || _state == FutureState.Error)
|
||||
{
|
||||
callback(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_complationCallbacks.Contains(callback))
|
||||
_complationCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
#pragma warning disable 1998
|
||||
|
||||
/// <summary>
|
||||
/// Begins running a given function on a background thread to resolve the future's value, as long
|
||||
/// as it is still in the Pending state.
|
||||
/// </summary>
|
||||
/// <param name="func">The function that will retrieve the desired value.</param>
|
||||
public IFuture<T> Process(Func<T> func)
|
||||
{
|
||||
if (_state != FutureState.Pending)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot process a future that isn't in the Pending state.");
|
||||
}
|
||||
|
||||
BeginProcess();
|
||||
_processFunc = func;
|
||||
|
||||
#if NETFX_CORE
|
||||
#pragma warning disable 4014
|
||||
Windows.System.Threading.ThreadPool.RunAsync(ThreadFunc);
|
||||
#pragma warning restore 4014
|
||||
#else
|
||||
System.Threading.ThreadPool.QueueUserWorkItem(ThreadFunc);
|
||||
#endif
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private
|
||||
#if NETFX_CORE
|
||||
async
|
||||
#endif
|
||||
void ThreadFunc(object param)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Directly call the Impl version to avoid the state validation of the public method
|
||||
AssignImpl(_processFunc());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Directly call the Impl version to avoid the state validation of the public method
|
||||
FailImpl(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_processFunc = null;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore 1998
|
||||
|
||||
/// <summary>
|
||||
/// Allows manually assigning a value to a future, as long as it is still in the pending state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// There are times where you may not need to do background processing for a value. For example,
|
||||
/// you may have a cache of values and can just hand one out. In those cases you still want to
|
||||
/// return a future for the method signature, but can just call this method to fill in the future.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to assign the future.</param>
|
||||
public void Assign(T value)
|
||||
{
|
||||
if (_state != FutureState.Pending && _state != FutureState.Processing)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot assign a value to a future that isn't in the Pending or Processing state.");
|
||||
}
|
||||
|
||||
AssignImpl(value);
|
||||
}
|
||||
|
||||
public void BeginProcess(T initialItem = default(T))
|
||||
{
|
||||
_state = FutureState.Processing;
|
||||
_value = initialItem;
|
||||
}
|
||||
|
||||
public void AssignItem(T value)
|
||||
{
|
||||
_value = value;
|
||||
_error = null;
|
||||
|
||||
foreach (var callback in _itemCallbacks)
|
||||
callback(this.value);
|
||||
}
|
||||
|
||||
public void Finish()
|
||||
{
|
||||
_state = FutureState.Success;
|
||||
|
||||
FlushSuccessCallbacks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows manually failing a future, as long as it is still in the pending state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As with the Assign method, there are times where you may know a future value is a failure without
|
||||
/// doing any background work. In those cases you can simply fail the future manually and return it.
|
||||
/// </remarks>
|
||||
/// <param name="error">The exception to use to fail the future.</param>
|
||||
public void Fail(Exception error)
|
||||
{
|
||||
if (_state != FutureState.Pending && _state != FutureState.Processing)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot fail future that isn't in the Pending or Processing state.");
|
||||
}
|
||||
|
||||
FailImpl(error);
|
||||
}
|
||||
|
||||
private void AssignImpl(T value)
|
||||
{
|
||||
_value = value;
|
||||
_error = null;
|
||||
_state = FutureState.Success;
|
||||
|
||||
FlushSuccessCallbacks();
|
||||
}
|
||||
|
||||
private void FailImpl(Exception error)
|
||||
{
|
||||
_value = default(T);
|
||||
_error = error;
|
||||
_state = FutureState.Error;
|
||||
|
||||
FlushErrorCallbacks();
|
||||
}
|
||||
|
||||
private void FlushSuccessCallbacks()
|
||||
{
|
||||
foreach (var callback in _successCallbacks)
|
||||
callback(this.value);
|
||||
|
||||
FlushComplationCallbacks();
|
||||
}
|
||||
|
||||
private void FlushErrorCallbacks()
|
||||
{
|
||||
foreach (var callback in _errorCallbacks)
|
||||
callback(this.error);
|
||||
|
||||
FlushComplationCallbacks();
|
||||
}
|
||||
|
||||
private void FlushComplationCallbacks()
|
||||
{
|
||||
foreach (var callback in _complationCallbacks)
|
||||
callback(this);
|
||||
ClearCallbacks();
|
||||
}
|
||||
|
||||
private void ClearCallbacks()
|
||||
{
|
||||
_itemCallbacks.Clear();
|
||||
_successCallbacks.Clear();
|
||||
_errorCallbacks.Clear();
|
||||
_complationCallbacks.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
#if CSHARP_7_OR_LATER
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BestHTTP.Logger;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BestHTTP
|
||||
{
|
||||
public sealed class AsyncHTTPException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Status code of the server's response.
|
||||
/// </summary>
|
||||
public int StatusCode;
|
||||
|
||||
/// <summary>
|
||||
/// Content sent by the server.
|
||||
/// </summary>
|
||||
public string Content;
|
||||
|
||||
public AsyncHTTPException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AsyncHTTPException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public AsyncHTTPException(int statusCode, string message, string content)
|
||||
:base(message)
|
||||
{
|
||||
this.StatusCode = statusCode;
|
||||
this.Content = content;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("StatusCode: {0}, Message: {1}, Content: {2}, StackTrace: {3}", this.StatusCode, this.Message, this.Content, this.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
public static class HTTPRequestAsyncExtensions
|
||||
{
|
||||
public static Task<HTTPResponse> GetHTTPResponseAsync(this HTTPRequest request, CancellationToken token = default)
|
||||
{
|
||||
return CreateTask<HTTPResponse>(request, token, (req, resp, tcs) =>
|
||||
{
|
||||
switch (req.State)
|
||||
{
|
||||
// The request finished without any problem.
|
||||
case HTTPRequestStates.Finished:
|
||||
tcs.TrySetResult(resp);
|
||||
break;
|
||||
|
||||
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
|
||||
case HTTPRequestStates.Error:
|
||||
VerboseLogging(request, "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception"));
|
||||
|
||||
tcs.TrySetException(CreateException("No Exception", null, req.Exception));
|
||||
break;
|
||||
|
||||
// The request aborted, initiated by the user.
|
||||
case HTTPRequestStates.Aborted:
|
||||
VerboseLogging(request, "Request Aborted!");
|
||||
|
||||
tcs.TrySetCanceled();
|
||||
break;
|
||||
|
||||
// Connecting to the server is timed out.
|
||||
case HTTPRequestStates.ConnectionTimedOut:
|
||||
VerboseLogging(request, "Connection Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Connection Timed Out!"));
|
||||
break;
|
||||
|
||||
// The request didn't finished in the given time.
|
||||
case HTTPRequestStates.TimedOut:
|
||||
VerboseLogging(request, "Processing the request Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Processing the request Timed Out!"));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<string> GetAsStringAsync(this HTTPRequest request, CancellationToken token = default)
|
||||
{
|
||||
return CreateTask<string>(request, token, (req, resp, tcs) =>
|
||||
{
|
||||
switch (req.State)
|
||||
{
|
||||
// The request finished without any problem.
|
||||
case HTTPRequestStates.Finished:
|
||||
if (resp.IsSuccess)
|
||||
tcs.TrySetResult(resp.DataAsText);
|
||||
else
|
||||
tcs.TrySetException(CreateException("Request finished Successfully, but the server sent an error.", resp));
|
||||
break;
|
||||
|
||||
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
|
||||
case HTTPRequestStates.Error:
|
||||
VerboseLogging(request, "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception"));
|
||||
|
||||
tcs.TrySetException(CreateException("No Exception", null, req.Exception));
|
||||
break;
|
||||
|
||||
// The request aborted, initiated by the user.
|
||||
case HTTPRequestStates.Aborted:
|
||||
VerboseLogging(request, "Request Aborted!");
|
||||
|
||||
tcs.TrySetCanceled();
|
||||
break;
|
||||
|
||||
// Connecting to the server is timed out.
|
||||
case HTTPRequestStates.ConnectionTimedOut:
|
||||
VerboseLogging(request, "Connection Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Connection Timed Out!"));
|
||||
break;
|
||||
|
||||
// The request didn't finished in the given time.
|
||||
case HTTPRequestStates.TimedOut:
|
||||
VerboseLogging(request, "Processing the request Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Processing the request Timed Out!"));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<Texture2D> GetAsTexture2DAsync(this HTTPRequest request, CancellationToken token = default)
|
||||
{
|
||||
return CreateTask<Texture2D>(request, token, (req, resp, tcs) =>
|
||||
{
|
||||
switch (req.State)
|
||||
{
|
||||
// The request finished without any problem.
|
||||
case HTTPRequestStates.Finished:
|
||||
if (resp.IsSuccess)
|
||||
tcs.TrySetResult(resp.DataAsTexture2D);
|
||||
else
|
||||
tcs.TrySetException(CreateException("Request finished Successfully, but the server sent an error.", resp));
|
||||
break;
|
||||
|
||||
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
|
||||
case HTTPRequestStates.Error:
|
||||
VerboseLogging(request, "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception"));
|
||||
|
||||
tcs.TrySetException(CreateException("No Exception", null, req.Exception));
|
||||
break;
|
||||
|
||||
// The request aborted, initiated by the user.
|
||||
case HTTPRequestStates.Aborted:
|
||||
VerboseLogging(request, "Request Aborted!");
|
||||
|
||||
tcs.TrySetCanceled();
|
||||
break;
|
||||
|
||||
// Connecting to the server is timed out.
|
||||
case HTTPRequestStates.ConnectionTimedOut:
|
||||
VerboseLogging(request, "Connection Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Connection Timed Out!"));
|
||||
break;
|
||||
|
||||
// The request didn't finished in the given time.
|
||||
case HTTPRequestStates.TimedOut:
|
||||
VerboseLogging(request, "Processing the request Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Processing the request Timed Out!"));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<byte[]> GetRawDataAsync(this HTTPRequest request, CancellationToken token = default)
|
||||
{
|
||||
return CreateTask<byte[]>(request, token, (req, resp, tcs) =>
|
||||
{
|
||||
switch (req.State)
|
||||
{
|
||||
// The request finished without any problem.
|
||||
case HTTPRequestStates.Finished:
|
||||
if (resp.IsSuccess)
|
||||
tcs.TrySetResult(resp.Data);
|
||||
else
|
||||
tcs.TrySetException(CreateException("Request finished Successfully, but the server sent an error.", resp));
|
||||
break;
|
||||
|
||||
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
|
||||
case HTTPRequestStates.Error:
|
||||
VerboseLogging(request, "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception"));
|
||||
|
||||
tcs.TrySetException(CreateException("No Exception", null, req.Exception));
|
||||
break;
|
||||
|
||||
// The request aborted, initiated by the user.
|
||||
case HTTPRequestStates.Aborted:
|
||||
VerboseLogging(request, "Request Aborted!");
|
||||
|
||||
tcs.TrySetCanceled();
|
||||
break;
|
||||
|
||||
// Connecting to the server is timed out.
|
||||
case HTTPRequestStates.ConnectionTimedOut:
|
||||
VerboseLogging(request, "Connection Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Connection Timed Out!"));
|
||||
break;
|
||||
|
||||
// The request didn't finished in the given time.
|
||||
case HTTPRequestStates.TimedOut:
|
||||
VerboseLogging(request, "Processing the request Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Processing the request Timed Out!"));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public static Task<T> CreateTask<T>(HTTPRequest request, CancellationToken token, Action<HTTPRequest, HTTPResponse, TaskCompletionSource<T>> callback)
|
||||
{
|
||||
HTTPManager.Setup();
|
||||
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
|
||||
request.Callback = (req, resp) =>
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
tcs.SetCanceled();
|
||||
else
|
||||
callback(req, resp, tcs);
|
||||
};
|
||||
|
||||
if (token.CanBeCanceled)
|
||||
token.Register((state) => (state as HTTPRequest)?.Abort(), request);
|
||||
|
||||
if (request.State == HTTPRequestStates.Initial)
|
||||
request.Send();
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public static void VerboseLogging(HTTPRequest request, string str)
|
||||
{
|
||||
HTTPManager.Logger.Verbose("HTTPRequestAsyncExtensions", str, request.Context);
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public static Exception CreateException(string errorMessage, HTTPResponse resp = null, Exception ex = null)
|
||||
{
|
||||
if (resp != null)
|
||||
return new AsyncHTTPException(resp.StatusCode, resp.Message, resp.DataAsText);
|
||||
else if (ex != null)
|
||||
return new AsyncHTTPException(ex.Message, ex);
|
||||
else
|
||||
return new AsyncHTTPException(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Will parse a comma-separeted header value
|
||||
/// </summary>
|
||||
public sealed class HeaderParser : KeyValuePairList
|
||||
{
|
||||
public HeaderParser(string headerStr)
|
||||
{
|
||||
base.Values = Parse(headerStr);
|
||||
}
|
||||
|
||||
private List<HeaderValue> Parse(string headerStr)
|
||||
{
|
||||
List<HeaderValue> result = new List<HeaderValue>();
|
||||
|
||||
int pos = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (pos < headerStr.Length)
|
||||
{
|
||||
HeaderValue current = new HeaderValue();
|
||||
|
||||
current.Parse(headerStr, ref pos);
|
||||
|
||||
result.Add(current);
|
||||
}
|
||||
}
|
||||
catch(System.Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("HeaderParser - Parse", headerStr, ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BestHTTP.PlatformSupport.Text;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Used in string parsers. Its Value is optional.
|
||||
/// </summary>
|
||||
public sealed class HeaderValue
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
public List<HeaderValue> Options { get; set; }
|
||||
|
||||
public bool HasValue { get { return !string.IsNullOrEmpty(this.Value); } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public HeaderValue()
|
||||
{ }
|
||||
|
||||
public HeaderValue(string key)
|
||||
{
|
||||
this.Key = key;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Helper Functions
|
||||
|
||||
public void Parse(string headerStr, ref int pos)
|
||||
{
|
||||
ParseImplementation(headerStr, ref pos, true);
|
||||
}
|
||||
|
||||
public bool TryGetOption(string key, out HeaderValue option)
|
||||
{
|
||||
option = null;
|
||||
|
||||
if (Options == null || Options.Count == 0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < Options.Count; ++i)
|
||||
if (String.Equals(Options[i].Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
option = Options[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helper Functions
|
||||
|
||||
private void ParseImplementation(string headerStr, ref int pos, bool isOptionIsAnOption)
|
||||
{
|
||||
// According to https://tools.ietf.org/html/rfc7234#section-5.2.1.1
|
||||
// Max-Age has a form "max-age=5", but some (imgur.com specifically) sends it as "max-age:5"
|
||||
string key = headerStr.Read(ref pos, (ch) => ch != ';' && ch != '=' && ch != ':' && ch != ',', true);
|
||||
this.Key = key;
|
||||
|
||||
char? skippedChar = headerStr.Peek(pos - 1);
|
||||
bool isValue = skippedChar == '=' || skippedChar == ':';
|
||||
bool isOption = isOptionIsAnOption && skippedChar == ';';
|
||||
|
||||
while ((skippedChar != null && isValue || isOption) && pos < headerStr.Length)
|
||||
{
|
||||
|
||||
if (isValue)
|
||||
{
|
||||
string value = headerStr.ReadPossibleQuotedText(ref pos);
|
||||
this.Value = value;
|
||||
}
|
||||
else if (isOption)
|
||||
{
|
||||
HeaderValue option = new HeaderValue();
|
||||
option.ParseImplementation(headerStr, ref pos, false);
|
||||
|
||||
if (this.Options == null)
|
||||
this.Options = new List<HeaderValue>();
|
||||
|
||||
this.Options.Add(option);
|
||||
}
|
||||
|
||||
if (!isOptionIsAnOption)
|
||||
return;
|
||||
|
||||
skippedChar = headerStr.Peek(pos - 1);
|
||||
isValue = skippedChar == '=';
|
||||
isOption = isOptionIsAnOption && skippedChar == ';';
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.Options != null && this.Options.Count > 0)
|
||||
{
|
||||
StringBuilder sb = StringBuilderPool.Get(4); //new StringBuilder(4);
|
||||
sb.Append(Key);
|
||||
sb.Append("=");
|
||||
sb.Append(Value);
|
||||
|
||||
foreach(var option in Options)
|
||||
{
|
||||
sb.Append(";");
|
||||
sb.Append(option.ToString());
|
||||
}
|
||||
|
||||
return StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(Value))
|
||||
return Key + '=' + Value;
|
||||
else
|
||||
return Key;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
public interface IHeartbeat
|
||||
{
|
||||
void OnHeartbeatUpdate(TimeSpan dif);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A manager class that can handle subscribing and unsubscribeing in the same update.
|
||||
/// </summary>
|
||||
public sealed class HeartbeatManager
|
||||
{
|
||||
private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
||||
|
||||
private List<IHeartbeat> Heartbeats = new List<IHeartbeat>();
|
||||
private IHeartbeat[] UpdateArray;
|
||||
private DateTime LastUpdate = DateTime.MinValue;
|
||||
|
||||
public void Subscribe(IHeartbeat heartbeat)
|
||||
{
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (!Heartbeats.Contains(heartbeat))
|
||||
Heartbeats.Add(heartbeat);
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Unsubscribe(IHeartbeat heartbeat)
|
||||
{
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
Heartbeats.Remove(heartbeat);
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (LastUpdate == DateTime.MinValue)
|
||||
LastUpdate = DateTime.UtcNow;
|
||||
else
|
||||
{
|
||||
TimeSpan dif = DateTime.UtcNow - LastUpdate;
|
||||
LastUpdate = DateTime.UtcNow;
|
||||
|
||||
int count = 0;
|
||||
|
||||
rwLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (UpdateArray == null || UpdateArray.Length < Heartbeats.Count)
|
||||
Array.Resize(ref UpdateArray, Heartbeats.Count);
|
||||
|
||||
Heartbeats.CopyTo(0, UpdateArray, 0, Heartbeats.Count);
|
||||
|
||||
count = Heartbeats.Count;
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitReadLock();
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateArray[i].OnHeartbeatUpdate(dif);
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
Heartbeats.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for specialized parsers
|
||||
/// </summary>
|
||||
public class KeyValuePairList
|
||||
{
|
||||
public List<HeaderValue> Values { get; protected set; }
|
||||
|
||||
public bool TryGet(string valueKeyName, out HeaderValue @param)
|
||||
{
|
||||
@param = null;
|
||||
for (int i = 0; i < Values.Count; ++i)
|
||||
if (string.CompareOrdinal(Values[i].Key, valueKeyName) == 0)
|
||||
{
|
||||
@param = Values[i];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
namespace BestHTTP.Extensions
|
||||
{
|
||||
public sealed class PeekableIncomingSegmentStream : BufferSegmentStream
|
||||
{
|
||||
private int peek_listIdx;
|
||||
private int peek_pos;
|
||||
|
||||
public void BeginPeek()
|
||||
{
|
||||
peek_listIdx = 0;
|
||||
peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
|
||||
}
|
||||
|
||||
public int PeekByte()
|
||||
{
|
||||
if (base.bufferList.Count == 0)
|
||||
return -1;
|
||||
|
||||
var segment = base.bufferList[this.peek_listIdx];
|
||||
if (peek_pos >= segment.Offset + segment.Count)
|
||||
{
|
||||
if (base.bufferList.Count <= this.peek_listIdx + 1)
|
||||
return -1;
|
||||
|
||||
segment = base.bufferList[++this.peek_listIdx];
|
||||
this.peek_pos = segment.Offset;
|
||||
}
|
||||
|
||||
return segment.Data[this.peek_pos++];
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue