提交Unity 联机Pro

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

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e9dc42aeefc20544bb572bce27e3b7b1
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

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

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 424d976119ee3fc438fe98095013859f
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

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

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 26e64cd0d1567634fb40b1386ba0c48d
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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 browsers cached copy (unless the server responds with a fresher response),
// but if the server responds favourably, the network transfer is only a files 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

View File

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

View File

@@ -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

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7b141dab023cd9d438e0585b93cf2826
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 65fae491919869042be9bb2f969bf050
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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()
// {
// }
//}
}

View File

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

View File

@@ -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;
}
}

View File

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

View File

@@ -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;
}
}
}

View File

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

View File

@@ -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

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 24106f78264881a419f83ef4b760ded7
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2edfd4a45eb2f664da87da040d6fcb81
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,671 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using System;
using System.Collections.Generic;
using System.Threading;
using System.Collections.Concurrent;
using BestHTTP.Extensions;
using BestHTTP.Core;
using BestHTTP.PlatformSupport.Memory;
using BestHTTP.Logger;
using BestHTTP.PlatformSupport.Threading;
namespace BestHTTP.Connections.HTTP2
{
public sealed class HTTP2Handler : IHTTPRequestHandler
{
public bool HasCustomRequestProcessor { get { return true; } }
public KeepAliveHeader KeepAlive { get { return null; } }
public bool CanProcessMultiple { get { return this.goAwaySentAt == DateTime.MaxValue && this.isRunning; } }
// Connection preface starts with the string PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n).
private static readonly byte[] MAGIC = new byte[24] { 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a };
public const UInt32 MaxValueFor31Bits = 0xFFFFFFFF >> 1;
public double Latency { get; private set; }
public HTTP2SettingsManager settings;
public HPACKEncoder HPACKEncoder;
public LoggingContext Context { get; private set; }
private DateTime lastPingSent = DateTime.MinValue;
private TimeSpan pingFrequency = TimeSpan.MaxValue; // going to be overridden in RunHandler
private int waitingForPingAck = 0;
public static int RTTBufferCapacity = 5;
private CircularBuffer<double> rtts = new CircularBuffer<double>(RTTBufferCapacity);
private volatile bool isRunning;
private AutoResetEvent newFrameSignal = new AutoResetEvent(false);
private ConcurrentQueue<HTTPRequest> requestQueue = new ConcurrentQueue<HTTPRequest>();
private List<HTTP2Stream> clientInitiatedStreams = new List<HTTP2Stream>();
private ConcurrentQueue<HTTP2FrameHeaderAndPayload> newFrames = new ConcurrentQueue<HTTP2FrameHeaderAndPayload>();
private List<HTTP2FrameHeaderAndPayload> outgoingFrames = new List<HTTP2FrameHeaderAndPayload>();
private UInt32 remoteWindow;
private DateTime lastInteraction;
private DateTime goAwaySentAt = DateTime.MaxValue;
private HTTPConnection conn;
private int threadExitCount;
private TimeSpan MaxGoAwayWaitTime { get { return this.goAwaySentAt == DateTime.MaxValue ? TimeSpan.MaxValue : TimeSpan.FromMilliseconds(Math.Max(this.Latency * 2.5, 1500)); } }
// https://httpwg.org/specs/rfc7540.html#StreamIdentifiers
// Streams initiated by a client MUST use odd-numbered stream identifiers
// With an initial value of -1, the first client initiated stream's id going to be 1.
private long LastStreamId = -1;
public HTTP2Handler(HTTPConnection conn)
{
this.Context = new LoggingContext(this);
this.conn = conn;
this.isRunning = true;
this.settings = new HTTP2SettingsManager(this);
Process(this.conn.CurrentRequest);
}
public void Process(HTTPRequest request)
{
HTTPManager.Logger.Information("HTTP2Handler", "Process request called", this.Context, request.Context);
request.QueuedAt = DateTime.MinValue;
request.ProcessingStarted = this.lastInteraction = DateTime.UtcNow;
this.requestQueue.Enqueue(request);
// Wee might added the request to a dead queue, signaling would be pointless.
// When the ConnectionEventHelper processes the Close state-change event
// requests in the queue going to be resent. (We should avoid resending the request just right now,
// as it might still select this connection/handler resulting in a infinite loop.)
if (Volatile.Read(ref this.threadExitCount) == 0)
this.newFrameSignal.Set();
}
public void SignalRunnerThread()
{
this.newFrameSignal?.Set();
}
public void RunHandler()
{
HTTPManager.Logger.Information("HTTP2Handler", "Processing thread up and running!", this.Context);
ThreadedRunner.SetThreadName("BestHTTP.HTTP2 Process");
PlatformSupport.Threading.ThreadedRunner.RunLongLiving(ReadThread);
try
{
bool atLeastOneStreamHasAFrameToSend = true;
this.HPACKEncoder = new HPACKEncoder(this, this.settings);
// https://httpwg.org/specs/rfc7540.html#InitialWindowSize
// The connection flow-control window is also 65,535 octets.
this.remoteWindow = this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
// we want to pack as many data as we can in one tcp segment, but setting the buffer's size too high
// we might keep data too long and send them in bursts instead of in a steady stream.
// Keeping it too low might result in a full tcp segment and one with very low payload
// Is it possible that one full tcp segment sized buffer would be the best, or multiple of it.
// It would keep the network busy without any fragments. The ethernet layer has a maximum of 1500 bytes,
// but there's two layers of 20 byte headers each, so as a theoretical maximum it's 1500-20-20 bytes.
// On the other hand, if the buffer is small (1-2), that means that for larger data, we have to do a lot
// of system calls, in that case a larger buffer might be better. Still, if we are not cpu bound,
// a well saturated network might serve us better.
using (WriteOnlyBufferedStream bufferedStream = new WriteOnlyBufferedStream(this.conn.connector.Stream, 1024 * 1024 /*1500 - 20 - 20*/))
{
// The client connection preface starts with a sequence of 24 octets
bufferedStream.Write(MAGIC, 0, MAGIC.Length);
// This sequence MUST be followed by a SETTINGS frame (Section 6.5), which MAY be empty.
// The client sends the client connection preface immediately upon receipt of a
// 101 (Switching Protocols) response (indicating a successful upgrade)
// or as the first application data octets of a TLS connection
// Set streams' initial window size to its maximum.
this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] = HTTPManager.HTTP2Settings.InitialStreamWindowSize;
this.settings.InitiatedMySettings[HTTP2Settings.MAX_CONCURRENT_STREAMS] = HTTPManager.HTTP2Settings.MaxConcurrentStreams;
this.settings.InitiatedMySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] = (uint)(HTTPManager.HTTP2Settings.EnableConnectProtocol ? 1 : 0);
this.settings.InitiatedMySettings[HTTP2Settings.ENABLE_PUSH] = 0;
this.settings.SendChanges(this.outgoingFrames);
this.settings.RemoteSettings.OnSettingChangedEvent += OnRemoteSettingChanged;
// The default window size for the whole connection is 65535 bytes,
// but we want to set it to the maximum possible value.
Int64 initialConnectionWindowSize = HTTPManager.HTTP2Settings.InitialConnectionWindowSize;
// yandex.ru returns with an FLOW_CONTROL_ERROR (3) error when the plugin tries to set the connection window to 2^31 - 1
// and works only with a maximum value of 2^31 - 10Mib (10 * 1024 * 1024).
if (initialConnectionWindowSize == HTTP2Handler.MaxValueFor31Bits)
initialConnectionWindowSize -= 10 * 1024 * 1024;
Int64 diff = initialConnectionWindowSize - 65535;
if (diff > 0)
this.outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(0, (UInt32)diff));
this.pingFrequency = HTTPManager.HTTP2Settings.PingFrequency;
while (this.isRunning)
{
DateTime now = DateTime.UtcNow;
if (!atLeastOneStreamHasAFrameToSend)
{
// buffered stream will call flush automatically if its internal buffer is full.
// But we have to make it sure that we flush remaining data before we go to sleep.
bufferedStream.Flush();
// Wait until we have to send the next ping, OR a new frame is received on the read thread.
// lastPingSent Now lastPingSent+frequency lastPingSent+Ping timeout
//----|---------------------|---------------|----------------------|----------------------|------------|
// lastInteraction lastInteraction + MaxIdleTime
var sendPingAt = this.lastPingSent + this.pingFrequency;
var timeoutAt = this.waitingForPingAck != 0 ? this.lastPingSent + HTTPManager.HTTP2Settings.Timeout : DateTime.MaxValue;
var nextPingInteraction = sendPingAt < timeoutAt ? sendPingAt : timeoutAt;
var disconnectByIdleAt = this.lastInteraction + HTTPManager.HTTP2Settings.MaxIdleTime;
var nextDueClientInteractionAt = nextPingInteraction < disconnectByIdleAt ? nextPingInteraction : disconnectByIdleAt;
int wait = (int)(nextDueClientInteractionAt - now).TotalMilliseconds;
wait = (int)Math.Min(wait, this.MaxGoAwayWaitTime.TotalMilliseconds);
TimeSpan nextStreamInteraction = TimeSpan.MaxValue;
for (int i = 0; i < this.clientInitiatedStreams.Count; i++)
{
var streamInteraction = this.clientInitiatedStreams[i].NextInteraction;
if (streamInteraction < nextStreamInteraction)
nextStreamInteraction = streamInteraction;
}
wait = (int)Math.Min(wait, nextStreamInteraction.TotalMilliseconds);
if (wait >= 1)
{
if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
HTTPManager.Logger.Information("HTTP2Handler", string.Format("Sleeping for {0:N0}ms", wait), this.Context);
this.newFrameSignal.WaitOne(wait);
now = DateTime.UtcNow;
}
}
// Don't send a new ping until a pong isn't received for the last one
if (now - this.lastPingSent >= this.pingFrequency && Interlocked.CompareExchange(ref this.waitingForPingAck, 1, 0) == 0)
{
this.lastPingSent = now;
var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.None);
BufferHelper.SetLong(frame.Payload, 0, now.Ticks);
this.outgoingFrames.Add(frame);
}
// If no pong received in a (configurable) reasonable time, treat the connection broken
if (this.waitingForPingAck != 0 && now - this.lastPingSent >= HTTPManager.HTTP2Settings.Timeout)
throw new TimeoutException("Ping ACK isn't received in time!");
// Process received frames
HTTP2FrameHeaderAndPayload header;
while (this.newFrames.TryDequeue(out header))
{
if (header.StreamId > 0)
{
HTTP2Stream http2Stream = FindStreamById(header.StreamId);
// Add frame to the stream, so it can process it when its Process function is called
if (http2Stream != null)
{
http2Stream.AddFrame(header, this.outgoingFrames);
}
else
{
// Error? It's possible that we closed and removed the stream while the server was in the middle of sending frames
if (HTTPManager.Logger.Level == Loglevels.All)
HTTPManager.Logger.Warning("HTTP2Handler", string.Format("No stream found for id: {0}! Can't deliver frame: {1}", header.StreamId, header), this.Context, http2Stream.Context);
}
}
else
{
switch (header.Type)
{
case HTTP2FrameTypes.SETTINGS:
this.settings.Process(header, this.outgoingFrames);
PluginEventHelper.EnqueuePluginEvent(
new PluginEventInfo(PluginEvents.HTTP2ConnectProtocol,
new HTTP2ConnectProtocolInfo(this.conn.LastProcessedUri.Host,
this.settings.MySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] == 1 && this.settings.RemoteSettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] == 1)));
break;
case HTTP2FrameTypes.PING:
var pingFrame = HTTP2FrameHelper.ReadPingFrame(header);
// https://httpwg.org/specs/rfc7540.html#PING
// if it wasn't an ack for our ping, we have to send one
if ((pingFrame.Flags & HTTP2PingFlags.ACK) == 0)
{
var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.ACK);
Array.Copy(pingFrame.OpaqueData, 0, frame.Payload, 0, pingFrame.OpaqueDataLength);
this.outgoingFrames.Add(frame);
}
BufferPool.Release(pingFrame.OpaqueData);
break;
case HTTP2FrameTypes.WINDOW_UPDATE:
var windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(header);
this.remoteWindow += windowUpdateFrame.WindowSizeIncrement;
break;
case HTTP2FrameTypes.GOAWAY:
// parse the frame, so we can print out detailed information
HTTP2GoAwayFrame goAwayFrame = HTTP2FrameHelper.ReadGoAwayFrame(header);
HTTPManager.Logger.Information("HTTP2Handler", "Received GOAWAY frame: " + goAwayFrame.ToString(), this.Context);
string msg = string.Format("Server closing the connection! Error code: {0} ({1}) Additonal Debug Data: {2}", goAwayFrame.Error, goAwayFrame.ErrorCode, new BufferSegment(goAwayFrame.AdditionalDebugData, 0, (int)goAwayFrame.AdditionalDebugDataLength));
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
this.clientInitiatedStreams[i].Abort(msg);
this.clientInitiatedStreams.Clear();
// set the running flag to false, so the thread can exit
this.isRunning = false;
BufferPool.Release(goAwayFrame.AdditionalDebugData);
//this.conn.State = HTTPConnectionStates.Closed;
break;
case HTTP2FrameTypes.ALT_SVC:
//HTTP2AltSVCFrame altSvcFrame = HTTP2FrameHelper.ReadAltSvcFrame(header);
// Implement
//HTTPManager.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.AltSvcHeader, new AltSvcEventInfo(altSvcFrame.Origin, ))
break;
}
if (header.Payload != null)
BufferPool.Release(header.Payload);
}
}
UInt32 maxConcurrentStreams = Math.Min(HTTPManager.HTTP2Settings.MaxConcurrentStreams, this.settings.RemoteSettings[HTTP2Settings.MAX_CONCURRENT_STREAMS]);
// pre-test stream count to lock only when truly needed.
if (this.clientInitiatedStreams.Count < maxConcurrentStreams && this.isRunning)
{
// grab requests from queue
HTTPRequest request;
while (this.clientInitiatedStreams.Count < maxConcurrentStreams && this.requestQueue.TryDequeue(out request))
{
HTTP2Stream newStream = null;
#if !BESTHTTP_DISABLE_WEBSOCKET
if (request.Tag is WebSocket.OverHTTP2)
{
newStream = new HTTP2WebSocketStream((UInt32)Interlocked.Add(ref LastStreamId, 2), this, this.settings, this.HPACKEncoder);
}
else
#endif
{
newStream = new HTTP2Stream((UInt32)Interlocked.Add(ref LastStreamId, 2), this, this.settings, this.HPACKEncoder);
}
newStream.Assign(request);
this.clientInitiatedStreams.Add(newStream);
}
}
// send any settings changes
this.settings.SendChanges(this.outgoingFrames);
atLeastOneStreamHasAFrameToSend = false;
// process other streams
// Room for improvement Streams should be processed by their priority!
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
{
var stream = this.clientInitiatedStreams[i];
stream.Process(this.outgoingFrames);
// remove closed, empty streams (not enough to check the closed flag, a closed stream still can contain frames to send)
if (stream.State == HTTP2StreamStates.Closed && !stream.HasFrameToSend)
{
this.clientInitiatedStreams.RemoveAt(i--);
stream.Removed();
}
atLeastOneStreamHasAFrameToSend |= stream.HasFrameToSend;
this.lastInteraction = DateTime.UtcNow;
}
// If we encounter a data frame that too large for the current remote window, we have to stop
// sending all data frames as we could send smaller data frames before the large ones.
// Room for improvement: An improvement would be here to stop data frame sending per-stream.
bool haltDataSending = false;
if (this.ShutdownType == ShutdownTypes.Running && now - this.lastInteraction >= HTTPManager.HTTP2Settings.MaxIdleTime)
{
this.lastInteraction = DateTime.UtcNow;
HTTPManager.Logger.Information("HTTP2Handler", "Reached idle time, sending GoAway frame!", this.Context);
this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR));
this.goAwaySentAt = DateTime.UtcNow;
}
// https://httpwg.org/specs/rfc7540.html#GOAWAY
// Endpoints SHOULD always send a GOAWAY frame before closing a connection so that the remote peer can know whether a stream has been partially processed or not.
if (this.ShutdownType == ShutdownTypes.Gentle)
{
HTTPManager.Logger.Information("HTTP2Handler", "Connection abort requested, sending GoAway frame!", this.Context);
this.outgoingFrames.Clear();
this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR));
this.goAwaySentAt = DateTime.UtcNow;
}
if (this.isRunning && now - goAwaySentAt >= this.MaxGoAwayWaitTime)
{
HTTPManager.Logger.Information("HTTP2Handler", "No GoAway frame received back. Really quitting now!", this.Context);
this.isRunning = false;
//conn.State = HTTPConnectionStates.Closed;
}
uint streamWindowUpdates = 0;
// Go through all the collected frames and send them.
for (int i = 0; i < this.outgoingFrames.Count; ++i)
{
var frame = this.outgoingFrames[i];
if (HTTPManager.Logger.Level <= Logger.Loglevels.All && frame.Type != HTTP2FrameTypes.DATA /*&& frame.Type != HTTP2FrameTypes.PING*/)
HTTPManager.Logger.Information("HTTP2Handler", "Sending frame: " + frame.ToString(), this.Context);
// post process frames
switch (frame.Type)
{
case HTTP2FrameTypes.DATA:
if (haltDataSending)
continue;
// if the tracked remoteWindow is smaller than the frame's payload, we stop sending
// data frames until we receive window-update frames
if (frame.PayloadLength > this.remoteWindow)
{
haltDataSending = true;
HTTPManager.Logger.Warning("HTTP2Handler", string.Format("Data sending halted for this round. Remote Window: {0:N0}, frame: {1}", this.remoteWindow, frame.ToString()), this.Context);
continue;
}
break;
case HTTP2FrameTypes.WINDOW_UPDATE:
if (frame.StreamId > 0)
streamWindowUpdates += BufferHelper.ReadUInt31(frame.Payload, 0);
break;
}
this.outgoingFrames.RemoveAt(i--);
using (var buffer = HTTP2FrameHelper.HeaderAsBinary(frame))
bufferedStream.Write(buffer.Data, 0, buffer.Length);
if (frame.PayloadLength > 0)
{
bufferedStream.Write(frame.Payload, (int)frame.PayloadOffset, (int)frame.PayloadLength);
if (!frame.DontUseMemPool)
BufferPool.Release(frame.Payload);
}
if (frame.Type == HTTP2FrameTypes.DATA)
this.remoteWindow -= frame.PayloadLength;
}
if (streamWindowUpdates > 0)
{
var frame = HTTP2FrameHelper.CreateWindowUpdateFrame(0, streamWindowUpdates);
if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
HTTPManager.Logger.Information("HTTP2Handler", "Sending frame: " + frame.ToString(), this.Context);
using (var buffer = HTTP2FrameHelper.HeaderAsBinary(frame))
bufferedStream.Write(buffer.Data, 0, buffer.Length);
bufferedStream.Write(frame.Payload, (int)frame.PayloadOffset, (int)frame.PayloadLength);
if (!frame.DontUseMemPool)
BufferPool.Release(frame.Payload);
}
} // while (this.isRunning)
bufferedStream.Flush();
}
}
catch (Exception ex)
{
// Log out the exception if it's a non-expected one.
if (this.ShutdownType == ShutdownTypes.Running && this.goAwaySentAt == DateTime.MaxValue && !HTTPManager.IsQuitting)
HTTPManager.Logger.Exception("HTTP2Handler", "Sender thread", ex, this.Context);
}
finally
{
TryToCleanup();
HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing - cleaning up remaining request...", this.Context);
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
this.clientInitiatedStreams[i].Abort("Connection closed unexpectedly");
this.clientInitiatedStreams.Clear();
HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing", this.Context);
}
try
{
if (this.conn != null && this.conn.connector != null)
{
// Works in the new runtime
if (this.conn.connector.TopmostStream != null)
using (this.conn.connector.TopmostStream) { }
// Works in the old runtime
if (this.conn.connector.Stream != null)
using (this.conn.connector.Stream) { }
}
}
catch
{ }
}
private void OnRemoteSettingChanged(HTTP2SettingsRegistry registry, HTTP2Settings setting, uint oldValue, uint newValue)
{
switch(setting)
{
case HTTP2Settings.INITIAL_WINDOW_SIZE:
this.remoteWindow = newValue - (oldValue - this.remoteWindow);
break;
}
}
private void ReadThread()
{
try
{
ThreadedRunner.SetThreadName("BestHTTP.HTTP2 Read");
HTTPManager.Logger.Information("HTTP2Handler", "Reader thread up and running!", this.Context);
while (this.isRunning)
{
HTTP2FrameHeaderAndPayload header = HTTP2FrameHelper.ReadHeader(this.conn.connector.Stream);
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information && header.Type != HTTP2FrameTypes.DATA /*&& header.Type != HTTP2FrameTypes.PING*/)
HTTPManager.Logger.Information("HTTP2Handler", "New frame received: " + header.ToString(), this.Context);
// Add the new frame to the queue. Processing it on the write thread gives us the advantage that
// we don't have to deal with too much locking.
this.newFrames.Enqueue(header);
// ping write thread to process the new frame
this.newFrameSignal.Set();
switch (header.Type)
{
// Handle pongs on the read thread, so no additional latency is added to the rtt calculation.
case HTTP2FrameTypes.PING:
var pingFrame = HTTP2FrameHelper.ReadPingFrame(header);
if ((pingFrame.Flags & HTTP2PingFlags.ACK) != 0)
{
if (Interlocked.CompareExchange(ref this.waitingForPingAck, 0, 1) == 0)
break; // waitingForPingAck was 0 == aren't expecting a ping ack!
// it was an ack, payload must contain what we sent
var ticks = BufferHelper.ReadLong(pingFrame.OpaqueData, 0);
// the difference between the current time and the time when the ping message is sent
TimeSpan diff = TimeSpan.FromTicks(DateTime.UtcNow.Ticks - ticks);
// add it to the buffer
this.rtts.Add(diff.TotalMilliseconds);
// and calculate the new latency
this.Latency = CalculateLatency();
HTTPManager.Logger.Verbose("HTTP2Handler", string.Format("Latency: {0:F2}ms, RTT buffer: {1}", this.Latency, this.rtts.ToString()), this.Context);
}
BufferPool.Release(pingFrame.OpaqueData);
break;
case HTTP2FrameTypes.GOAWAY:
// Just exit from this thread. The processing thread will handle the frame too.
// Risking a double release here if the processing thread also consumed the goaway frame
//if (Volatile.Read(ref this.threadExitCount) > 0)
// BufferPool.Release(header.Payload);
return;
}
}
}
catch //(Exception ex)
{
//HTTPManager.Logger.Exception("HTTP2Handler", "", ex, this.Context);
//this.isRunning = false;
}
finally
{
TryToCleanup();
HTTPManager.Logger.Information("HTTP2Handler", "Reader thread closing", this.Context);
}
}
private void TryToCleanup()
{
this.isRunning = false;
// First thread closing notifies the ConnectionEventHelper
int counter = Interlocked.Increment(ref this.threadExitCount);
switch(counter)
{
case 1:
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, HTTPConnectionStates.Closed));
break;
// Last thread closes the AutoResetEvent
case 2:
if (this.newFrameSignal != null)
this.newFrameSignal.Close();
this.newFrameSignal = null;
while (this.newFrames.TryDequeue(out var frame))
BufferPool.Release(frame.Payload);
break;
default:
HTTPManager.Logger.Warning("HTTP2Handler", String.Format("TryToCleanup - counter is {0}!", counter));
break;
}
}
private double CalculateLatency()
{
if (this.rtts.Count == 0)
return 0;
double sumLatency = 0;
for (int i = 0; i < this.rtts.Count; ++i)
sumLatency += this.rtts[i];
return sumLatency / this.rtts.Count;
}
HTTP2Stream FindStreamById(UInt32 streamId)
{
for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
{
var stream = this.clientInitiatedStreams[i];
if (stream.Id == streamId)
return stream;
}
return null;
}
public ShutdownTypes ShutdownType { get; private set; }
public void Shutdown(ShutdownTypes type)
{
this.ShutdownType = type;
switch(this.ShutdownType)
{
case ShutdownTypes.Gentle:
this.newFrameSignal.Set();
break;
case ShutdownTypes.Immediate:
this.conn.connector.Stream.Dispose();
break;
}
}
public void Dispose()
{
HTTPRequest request = null;
while (this.requestQueue.TryDequeue(out request))
{
HTTPManager.Logger.Information("HTTP2Handler", string.Format("Dispose - Request '{0}' IsCancellationRequested: {1}", request.CurrentUri.ToString(), request.IsCancellationRequested.ToString()), this.Context);
if (request.IsCancellationRequested)
{
request.Response = null;
request.State = HTTPRequestStates.Aborted;
}
else
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend));
}
}
}
}
#endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,657 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
using System;
using System.Collections.Generic;
using BestHTTP.Core;
using BestHTTP.PlatformSupport.Memory;
using BestHTTP.Logger;
#if !BESTHTTP_DISABLE_CACHING
using BestHTTP.Caching;
#endif
using BestHTTP.Timings;
namespace BestHTTP.Connections.HTTP2
{
// https://httpwg.org/specs/rfc7540.html#StreamStates
//
// Idle
// |
// V
// Open
// Receive END_STREAM / | \ Send END_STREAM
// v |R V
// Half Closed Remote |S Half Closed Locale
// \ |T /
// Send END_STREAM | RST_STREAM \ | / Receive END_STREAM | RST_STREAM
// Receive RST_STREAM \ | / Send RST_STREAM
// V
// Closed
//
// IDLE -> send headers -> OPEN -> send data -> HALF CLOSED - LOCAL -> receive headers -> receive Data -> CLOSED
// | ^ | ^
// +-------------------------------------+ +-----------------------------+
// END_STREAM flag present? END_STREAM flag present?
//
public enum HTTP2StreamStates
{
Idle,
//ReservedLocale,
//ReservedRemote,
Open,
HalfClosedLocal,
HalfClosedRemote,
Closed
}
public class HTTP2Stream
{
public UInt32 Id { get; private set; }
public HTTP2StreamStates State {
get { return this._state; }
protected set {
var oldState = this._state;
this._state = value;
if (oldState != this._state)
{
//this.lastStateChangedAt = DateTime.Now;
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] State changed from {1} to {2}", this.Id, oldState, this._state), this.Context, this.AssignedRequest.Context, this.parent.Context);
}
}
}
private HTTP2StreamStates _state;
//protected DateTime lastStateChangedAt;
//protected TimeSpan TimeSpentInCurrentState { get { return DateTime.Now - this.lastStateChangedAt; } }
/// <summary>
/// This flag is checked by the connection to decide whether to do a new processing-frame sending round before sleeping until new data arrives
/// </summary>
public virtual bool HasFrameToSend
{
get
{
// Don't let the connection sleep until
return this.outgoing.Count > 0 || // we already booked at least one frame in advance
(this.State == HTTP2StreamStates.Open && this.remoteWindow > 0 && this.lastReadCount > 0); // we are in the middle of sending request data
}
}
/// <summary>
/// Next interaction scheduled by the stream relative to *now*. Its default is TimeSpan.MaxValue == no interaction.
/// </summary>
public virtual TimeSpan NextInteraction { get; } = TimeSpan.MaxValue;
public HTTPRequest AssignedRequest { get; protected set; }
public LoggingContext Context { get; protected set; }
protected bool isStreamedDownload;
protected uint downloaded;
protected HTTPRequest.UploadStreamInfo uploadStreamInfo;
protected HTTP2SettingsManager settings;
protected HPACKEncoder encoder;
// Outgoing frames. The stream will send one frame per Process call, but because one step might be able to
// generate more than one frames, we use a list.
protected Queue<HTTP2FrameHeaderAndPayload> outgoing = new Queue<HTTP2FrameHeaderAndPayload>();
protected Queue<HTTP2FrameHeaderAndPayload> incomingFrames = new Queue<HTTP2FrameHeaderAndPayload>();
protected FramesAsStreamView headerView;
protected FramesAsStreamView dataView;
protected UInt32 localWindow;
protected Int64 remoteWindow;
protected uint windowUpdateThreshold;
protected UInt32 sentData;
protected bool isRSTFrameSent;
protected bool isEndSTRReceived;
protected HTTP2Response response;
protected HTTP2Handler parent;
protected int lastReadCount;
/// <summary>
/// Constructor to create a client stream.
/// </summary>
public HTTP2Stream(UInt32 id, HTTP2Handler parentHandler, HTTP2SettingsManager registry, HPACKEncoder hpackEncoder)
{
this.Id = id;
this.parent = parentHandler;
this.settings = registry;
this.encoder = hpackEncoder;
this.remoteWindow = this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
this.settings.RemoteSettings.OnSettingChangedEvent += OnRemoteSettingChanged;
// Room for improvement: If INITIAL_WINDOW_SIZE is small (what we can consider a 'small' value?), threshold must be higher
this.windowUpdateThreshold = (uint)(this.remoteWindow / 2);
this.Context = new LoggingContext(this);
this.Context.Add("id", id);
}
public virtual void Assign(HTTPRequest request)
{
if (request.IsRedirected)
request.Timing.Add(TimingEventNames.Queued_For_Redirection);
else
request.Timing.Add(TimingEventNames.Queued);
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Request assigned to stream. Remote Window: {1:N0}. Uri: {2}", this.Id, this.remoteWindow, request.CurrentUri.ToString()), this.Context, request.Context, this.parent.Context);
this.AssignedRequest = request;
this.isStreamedDownload = request.UseStreaming && request.OnStreamingData != null;
this.downloaded = 0;
}
public void Process(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
if (this.AssignedRequest.IsCancellationRequested && !this.isRSTFrameSent)
{
// These two are already set in HTTPRequest's Abort().
//this.AssignedRequest.Response = null;
//this.AssignedRequest.State = this.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
this.outgoing.Clear();
if (this.State != HTTP2StreamStates.Idle)
this.outgoing.Enqueue(HTTP2FrameHelper.CreateRSTFrame(this.Id, HTTP2ErrorCodes.CANCEL));
// We can close the stream if already received headers, or not even sent one
if (this.State == HTTP2StreamStates.HalfClosedRemote || this.State == HTTP2StreamStates.Idle)
this.State = HTTP2StreamStates.Closed;
this.isRSTFrameSent = true;
}
// 1.) Go through incoming frames
ProcessIncomingFrames(outgoingFrames);
// 2.) Create outgoing frames based on the stream's state and the request processing state.
ProcessState(outgoingFrames);
// 3.) Send one frame per Process call
if (this.outgoing.Count > 0)
{
HTTP2FrameHeaderAndPayload frame = this.outgoing.Dequeue();
outgoingFrames.Add(frame);
// If END_Stream in header or data frame is present => half closed local
if ((frame.Type == HTTP2FrameTypes.HEADERS && (frame.Flags & (byte)HTTP2HeadersFlags.END_STREAM) != 0) ||
(frame.Type == HTTP2FrameTypes.DATA && (frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0))
{
this.State = HTTP2StreamStates.HalfClosedLocal;
}
}
}
public void AddFrame(HTTP2FrameHeaderAndPayload frame, List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
// Room for improvement: error check for forbidden frames (like settings) and stream state
this.incomingFrames.Enqueue(frame);
ProcessIncomingFrames(outgoingFrames);
}
public void Abort(string msg)
{
if (this.AssignedRequest.State != HTTPRequestStates.Processing)
{
// do nothing, its state is already set.
}
else if (this.AssignedRequest.IsCancellationRequested)
{
// These two are already set in HTTPRequest's Abort().
//this.AssignedRequest.Response = null;
//this.AssignedRequest.State = this.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
this.State = HTTP2StreamStates.Closed;
}
else if (this.AssignedRequest.Retries >= this.AssignedRequest.MaxRetries)
{
this.AssignedRequest.Response = null;
this.AssignedRequest.Exception = new Exception(msg);
this.AssignedRequest.State = HTTPRequestStates.Error;
this.State = HTTP2StreamStates.Closed;
}
else
{
this.AssignedRequest.Retries++;
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.Resend));
}
this.Removed();
}
protected void ProcessIncomingFrames(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
UInt32 windowUpdate = 0;
while (this.incomingFrames.Count > 0)
{
HTTP2FrameHeaderAndPayload frame = this.incomingFrames.Dequeue();
if ((this.isRSTFrameSent || this.AssignedRequest.IsCancellationRequested) && frame.Type != HTTP2FrameTypes.HEADERS && frame.Type != HTTP2FrameTypes.CONTINUATION)
{
BufferPool.Release(frame.Payload);
continue;
}
if (/*HTTPManager.Logger.Level == Logger.Loglevels.All && */frame.Type != HTTP2FrameTypes.DATA && frame.Type != HTTP2FrameTypes.WINDOW_UPDATE)
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Process - processing frame: {1}", this.Id, frame.ToString()), this.Context, this.AssignedRequest.Context, this.parent.Context);
switch (frame.Type)
{
case HTTP2FrameTypes.HEADERS:
case HTTP2FrameTypes.CONTINUATION:
if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open && this.State != HTTP2StreamStates.Idle)
{
// ERROR!
continue;
}
// payload will be released by the view
frame.DontUseMemPool = true;
if (this.headerView == null)
{
this.AssignedRequest.Timing.Add(TimingEventNames.Waiting_TTFB);
this.headerView = new FramesAsStreamView(new HeaderFrameView());
}
this.headerView.AddFrame(frame);
// END_STREAM may arrive sooner than an END_HEADERS, so we have to store that we already received it
if ((frame.Flags & (byte)HTTP2HeadersFlags.END_STREAM) != 0)
this.isEndSTRReceived = true;
if ((frame.Flags & (byte)HTTP2HeadersFlags.END_HEADERS) != 0)
{
List<KeyValuePair<string, string>> headers = new List<KeyValuePair<string, string>>();
try
{
this.encoder.Decode(this, this.headerView, headers);
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("HTTP2Stream", string.Format("[{0}] ProcessIncomingFrames - Header Frames: {1}, Encoder: {2}", this.Id, this.headerView.ToString(), this.encoder.ToString()), ex, this.Context, this.AssignedRequest.Context, this.parent.Context);
}
this.headerView.Close();
this.headerView = null;
this.AssignedRequest.Timing.Add(TimingEventNames.Headers);
if (this.isRSTFrameSent)
{
this.State = HTTP2StreamStates.Closed;
break;
}
if (this.response == null)
this.AssignedRequest.Response = this.response = new HTTP2Response(this.AssignedRequest, false);
this.response.AddHeaders(headers);
if (this.isEndSTRReceived)
{
// If there's any trailing header, no data frame has an END_STREAM flag
if (this.isStreamedDownload)
this.response.FinishProcessData();
PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTP2Stream, FramesAsStreamView>(FinishRequest, this, this.dataView);
this.dataView = null;
if (this.State == HTTP2StreamStates.HalfClosedLocal)
this.State = HTTP2StreamStates.Closed;
else
this.State = HTTP2StreamStates.HalfClosedRemote;
}
}
break;
case HTTP2FrameTypes.DATA:
ProcessIncomingDATAFrame(ref frame, ref windowUpdate);
break;
case HTTP2FrameTypes.WINDOW_UPDATE:
HTTP2WindowUpdateFrame windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(frame);
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Received Window Update: {1:N0}, new remoteWindow: {2:N0}, initial remote window: {3:N0}, total data sent: {4:N0}", this.Id, windowUpdateFrame.WindowSizeIncrement, this.remoteWindow + windowUpdateFrame.WindowSizeIncrement, this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE], this.sentData), this.Context, this.AssignedRequest.Context, this.parent.Context);
this.remoteWindow += windowUpdateFrame.WindowSizeIncrement;
break;
case HTTP2FrameTypes.RST_STREAM:
// https://httpwg.org/specs/rfc7540.html#RST_STREAM
// It's possible to receive an RST_STREAM on a closed stream. In this case, we have to ignore it.
if (this.State == HTTP2StreamStates.Closed)
break;
var rstStreamFrame = HTTP2FrameHelper.ReadRST_StreamFrame(frame);
//HTTPManager.Logger.Error("HTTP2Stream", string.Format("[{0}] RST Stream frame ({1}) received in state {2}!", this.Id, rstStreamFrame, this.State), this.Context, this.AssignedRequest.Context, this.parent.Context);
Abort(string.Format("RST_STREAM frame received! Error code: {0}({1})", rstStreamFrame.Error.ToString(), rstStreamFrame.ErrorCode));
break;
default:
HTTPManager.Logger.Warning("HTTP2Stream", string.Format("[{0}] Unexpected frame ({1}, Payload: {2}) in state {3}!", this.Id, frame, frame.PayloadAsHex(), this.State), this.Context, this.AssignedRequest.Context, this.parent.Context);
break;
}
if (!frame.DontUseMemPool)
BufferPool.Release(frame.Payload);
}
// 2023.07.08: Even if State is Closed, we should send back a window update.
// Not because of this stream, but for the global window update.
if (windowUpdate > 0 /*&& this.State != HTTP2StreamStates.Closed*/)
{
if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Sending window update: {1:N0}, current window: {2:N0}, initial window size: {3:N0}", this.Id, windowUpdate, this.localWindow, this.settings.MySettings[HTTP2Settings.INITIAL_WINDOW_SIZE]), this.Context, this.AssignedRequest.Context, this.parent.Context);
this.localWindow += windowUpdate;
outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, windowUpdate));
}
}
protected virtual void ProcessIncomingDATAFrame(ref HTTP2FrameHeaderAndPayload frame, ref uint windowUpdate)
{
if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open)
{
// ERROR!
return;
}
this.downloaded += frame.PayloadLength;
if (this.isStreamedDownload && frame.Payload != null && frame.PayloadLength > 0)
this.response.ProcessData(frame.Payload, (int)frame.PayloadLength);
// frame's buffer will be released by the frames view
frame.DontUseMemPool = !this.isStreamedDownload;
if (this.dataView == null && !this.isStreamedDownload)
this.dataView = new FramesAsStreamView(new DataFrameView());
if (!this.isStreamedDownload)
this.dataView.AddFrame(frame);
// Track received data, and if necessary(local window getting too low), send a window update frame
if (this.localWindow < frame.PayloadLength)
{
HTTPManager.Logger.Error("HTTP2Stream", string.Format("[{0}] Frame's PayloadLength ({1:N0}) is larger then local window ({2:N0}). Frame: {3}", this.Id, frame.PayloadLength, this.localWindow, frame), this.Context, this.AssignedRequest.Context, this.parent.Context);
}
else
this.localWindow -= frame.PayloadLength;
if ((frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0)
this.isEndSTRReceived = true;
// Window update logic.
// 1.) We could use a logic to only send window update(s) after a threshold is reached.
// When the initial window size is high enough to contain the whole or most of the result,
// sending back two window updates (connection and stream) after every data frame is pointless.
// 2.) On the other hand, window updates are cheap and works even when initial window size is low.
// (
if (this.isEndSTRReceived || this.localWindow <= this.windowUpdateThreshold)
windowUpdate += this.settings.MySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] - this.localWindow - windowUpdate;
if (this.isEndSTRReceived)
{
if (this.isStreamedDownload)
this.response.FinishProcessData();
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] All data arrived, data length: {1:N0}", this.Id, this.downloaded), this.Context, this.AssignedRequest.Context, this.parent.Context);
// create a short living thread to process the downloaded data:
PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTP2Stream, FramesAsStreamView>(FinishRequest, this, this.dataView);
this.dataView = null;
if (this.State == HTTP2StreamStates.HalfClosedLocal)
this.State = HTTP2StreamStates.Closed;
else
this.State = HTTP2StreamStates.HalfClosedRemote;
}
else if (this.AssignedRequest.OnDownloadProgress != null)
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest,
RequestEvents.DownloadProgress,
downloaded,
this.response.ExpectedContentLength));
}
protected void ProcessState(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
switch (this.State)
{
case HTTP2StreamStates.Idle:
UInt32 initiatedInitialWindowSize = this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
this.localWindow = initiatedInitialWindowSize;
// window update with a zero increment would be an error (https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE)
//if (HTTP2Connection.MaxValueFor31Bits > initiatedInitialWindowSize)
// this.outgoing.Enqueue(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, HTTP2Connection.MaxValueFor31Bits - initiatedInitialWindowSize));
//this.localWindow = HTTP2Connection.MaxValueFor31Bits;
#if !BESTHTTP_DISABLE_CACHING
// Setup cache control headers before we send out the request
if (!this.AssignedRequest.DisableCache)
HTTPCacheService.SetHeaders(this.AssignedRequest);
#endif
// hpack encode the request's headers
this.encoder.Encode(this, this.AssignedRequest, this.outgoing, this.Id);
// HTTP/2 uses DATA frames to carry message payloads.
// The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in HTTP/2.
this.uploadStreamInfo = this.AssignedRequest.GetUpStream();
//this.State = HTTP2StreamStates.Open;
if (this.uploadStreamInfo.Stream == null)
{
this.State = HTTP2StreamStates.HalfClosedLocal;
this.AssignedRequest.Timing.Add(TimingEventNames.Request_Sent);
}
else
{
this.State = HTTP2StreamStates.Open;
this.lastReadCount = 1;
}
break;
case HTTP2StreamStates.Open:
ProcessOpenState(outgoingFrames);
//HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] New DATA frame created! remoteWindow: {1:N0}", this.Id, this.remoteWindow), this.Context, this.AssignedRequest.Context, this.parent.Context);
break;
case HTTP2StreamStates.HalfClosedLocal:
break;
case HTTP2StreamStates.HalfClosedRemote:
break;
case HTTP2StreamStates.Closed:
break;
}
}
protected virtual void ProcessOpenState(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
{
// remote Window can be negative! See https://httpwg.org/specs/rfc7540.html#InitialWindowSize
if (this.remoteWindow <= 0)
{
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Skipping data sending as remote Window is {1}!", this.Id, this.remoteWindow), this.Context, this.AssignedRequest.Context, this.parent.Context);
return;
}
// This step will send one frame per OpenState call.
Int64 maxFrameSize = Math.Min(HTTPRequest.UploadChunkSize, Math.Min(this.remoteWindow, this.settings.RemoteSettings[HTTP2Settings.MAX_FRAME_SIZE]));
HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
frame.Type = HTTP2FrameTypes.DATA;
frame.StreamId = this.Id;
frame.Payload = BufferPool.Get(maxFrameSize, true);
// Expect a readCount of zero if it's end of the stream. But, to enable non-blocking scenario to wait for data, going to treat a negative value as no data.
this.lastReadCount = this.uploadStreamInfo.Stream.Read(frame.Payload, 0, (int)Math.Min(maxFrameSize, int.MaxValue));
if (this.lastReadCount <= 0)
{
BufferPool.Release(frame.Payload);
frame.Payload = null;
frame.PayloadLength = 0;
if (this.lastReadCount < 0)
return;
}
else
frame.PayloadLength = (UInt32)this.lastReadCount;
frame.PayloadOffset = 0;
frame.DontUseMemPool = false;
if (this.lastReadCount <= 0)
{
this.uploadStreamInfo.Stream.Dispose();
this.uploadStreamInfo = new HTTPRequest.UploadStreamInfo();
frame.Flags = (byte)(HTTP2DataFlags.END_STREAM);
this.State = HTTP2StreamStates.HalfClosedLocal;
this.AssignedRequest.Timing.Add(TimingEventNames.Request_Sent);
}
this.outgoing.Enqueue(frame);
this.remoteWindow -= frame.PayloadLength;
this.sentData += frame.PayloadLength;
if (this.AssignedRequest.OnUploadProgress != null)
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.UploadProgress, this.sentData, this.uploadStreamInfo.Length));
}
protected void OnRemoteSettingChanged(HTTP2SettingsRegistry registry, HTTP2Settings setting, uint oldValue, uint newValue)
{
switch (setting)
{
case HTTP2Settings.INITIAL_WINDOW_SIZE:
// https://httpwg.org/specs/rfc7540.html#InitialWindowSize
// "Prior to receiving a SETTINGS frame that sets a value for SETTINGS_INITIAL_WINDOW_SIZE,
// an endpoint can only use the default initial window size when sending flow-controlled frames."
// "In addition to changing the flow-control window for streams that are not yet active,
// a SETTINGS frame can alter the initial flow-control window size for streams with active flow-control windows
// (that is, streams in the "open" or "half-closed (remote)" state). When the value of SETTINGS_INITIAL_WINDOW_SIZE changes,
// a receiver MUST adjust the size of all stream flow-control windows that it maintains by the difference between the new value and the old value."
// So, if we created a stream before the remote peer's initial settings frame is received, we
// will adjust the window size. For example: initial window size by default is 65535, if we later
// receive a change to 1048576 (1 MB) we will increase the current remoteWindow by (1 048 576 - 65 535 =) 983 041
// But because initial window size in a setting frame can be smaller then the default 65535 bytes,
// the difference can be negative:
// "A change to SETTINGS_INITIAL_WINDOW_SIZE can cause the available space in a flow-control window to become negative.
// A sender MUST track the negative flow-control window and MUST NOT send new flow-controlled frames
// until it receives WINDOW_UPDATE frames that cause the flow-control window to become positive.
// For example, if the client sends 60 KB immediately on connection establishment
// and the server sets the initial window size to be 16 KB, the client will recalculate
// the available flow - control window to be - 44 KB on receipt of the SETTINGS frame.
// The client retains a negative flow-control window until WINDOW_UPDATE frames restore the
// window to being positive, after which the client can resume sending."
this.remoteWindow += newValue - oldValue;
HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Remote Setting's Initial Window Updated from {1:N0} to {2:N0}, diff: {3:N0}, new remoteWindow: {4:N0}, total data sent: {5:N0}", this.Id, oldValue, newValue, newValue - oldValue, this.remoteWindow, this.sentData), this.Context, this.AssignedRequest.Context, this.parent.Context);
break;
}
}
protected static void FinishRequest(HTTP2Stream stream, FramesAsStreamView dataStream)
{
try
{
if (dataStream != null)
{
try
{
stream.response.AddData(dataStream);
}
finally
{
dataStream.Close();
}
}
stream.AssignedRequest.Timing.Add(TimingEventNames.Response_Received);
bool resendRequest;
HTTPConnectionStates proposedConnectionStates; // ignored
KeepAliveHeader keepAliveHeader = null; // ignored
ConnectionHelper.HandleResponse("HTTP2Stream", stream.AssignedRequest, out resendRequest, out proposedConnectionStates, ref keepAliveHeader, stream.Context, stream.AssignedRequest.Context);
if (resendRequest && !stream.AssignedRequest.IsCancellationRequested)
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(stream.AssignedRequest, RequestEvents.Resend));
else if (stream.AssignedRequest.State == HTTPRequestStates.Processing && !stream.AssignedRequest.IsCancellationRequested)
stream.AssignedRequest.State = HTTPRequestStates.Finished;
else
{
// Already set in HTTPRequest's Abort().
//if (stream.AssignedRequest.State == HTTPRequestStates.Processing && stream.AssignedRequest.IsCancellationRequested)
// stream.AssignedRequest.State = stream.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
}
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("HTTP2Stream", "FinishRequest", ex, stream.AssignedRequest.Context);
}
}
public void Removed()
{
if (this.uploadStreamInfo.Stream != null)
{
this.uploadStreamInfo.Stream.Dispose();
this.uploadStreamInfo = new HTTPRequest.UploadStreamInfo();
}
// After receiving a RST_STREAM on a stream, the receiver MUST NOT send additional frames for that stream, with the exception of PRIORITY.
this.outgoing.Clear();
// https://github.com/Benedicht/BestHTTP-Issues/issues/77
// Unsubscribe from OnSettingChangedEvent to remove reference to this instance.
this.settings.RemoteSettings.OnSettingChangedEvent -= OnRemoteSettingChanged;
HTTPManager.Logger.Information("HTTP2Stream", "Stream removed: " + this.Id.ToString(), this.Context, this.AssignedRequest.Context, this.parent.Context);
}
}
}
#endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -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
}
}

View File

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

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 42fc2aaed5d783a48b2d61c1d7db8f23
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f9268b9adca26474ba1809bd1cb5a97b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 129f7b1f27cbedb43b7948eaf7a4d423
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8a8a4ac374e70b84b87d66c99e9dcc90
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More