- Initial commit
This commit is contained in:
@ -0,0 +1,182 @@
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.Logger;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
|
||||
namespace Best.HTTP.Shared.PlatformSupport.Network.Tcp.Streams
|
||||
{
|
||||
public sealed class FrameworkTLSByteForwarder : Stream, ITCPStreamerContentConsumer
|
||||
{
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => true;
|
||||
public override long Length { get { return this._length; } }
|
||||
private long _length;
|
||||
|
||||
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
|
||||
public long MaxBufferSize { get => Volatile.Read(ref this._maxBufferSize); set => Interlocked.Exchange(ref this._maxBufferSize, value); }
|
||||
private long _maxBufferSize;
|
||||
|
||||
private TCPStreamer _streamer;
|
||||
private LoggingContext _context;
|
||||
private ITCPStreamerContentConsumer _contentConsumer;
|
||||
|
||||
private Queue<BufferSegment> _segmentsToReadFrom = new Queue<BufferSegment>(8);
|
||||
|
||||
private AutoResetEvent _are = new AutoResetEvent(false);
|
||||
private ReaderWriterLockSlim _rws = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
||||
|
||||
private BufferSegment _currentReadSegment = BufferSegment.Empty;
|
||||
|
||||
public FrameworkTLSByteForwarder(TCPStreamer streamer, ITCPStreamerContentConsumer contentConsumer, long maxBufferSize, LoggingContext context)
|
||||
{
|
||||
this._streamer = streamer;
|
||||
this._streamer.ContentConsumer = this;
|
||||
|
||||
this._contentConsumer = contentConsumer;
|
||||
|
||||
this._context = context;
|
||||
this._maxBufferSize = maxBufferSize;
|
||||
}
|
||||
|
||||
public void /*ITCPStreamerContentConsumer.*/ Write(BufferSegment buffer)
|
||||
{
|
||||
using var _ = new AutoReleaseBuffer(buffer);
|
||||
this.Write(buffer.Data, buffer.Offset, buffer.Count);
|
||||
}
|
||||
|
||||
int _pullContentInProgress;
|
||||
|
||||
void PullContentFromStreamer()
|
||||
{
|
||||
//using var _ = new WriteLock(this._rws);
|
||||
if (Interlocked.CompareExchange(ref _pullContentInProgress, 1, 0) != 0)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
while (this._streamer.Length > 0 && this._length < this.MaxBufferSize)
|
||||
{
|
||||
var tmp = this._streamer.DequeueReceived();
|
||||
|
||||
if (tmp.Count <= 0)
|
||||
{
|
||||
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"DequeueReceived({tmp}) !", this._context);
|
||||
|
||||
BufferPool.Release(tmp);
|
||||
return;
|
||||
}
|
||||
|
||||
this._segmentsToReadFrom.Enqueue(tmp);
|
||||
this._length += tmp.Count;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _pullContentInProgress, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void /*ITCPStreamerContentConsumer.*/ OnContent(TCPStreamer streamer)
|
||||
{
|
||||
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"OnContent({streamer?.Length})", this._context);
|
||||
|
||||
PullContentFromStreamer();
|
||||
|
||||
this._are?.Set();
|
||||
|
||||
this._contentConsumer?.OnContent(streamer);
|
||||
}
|
||||
|
||||
public void /*ITCPStreamerContentConsumer.*/ OnConnectionClosed(TCPStreamer streamer) => this._contentConsumer?.OnConnectionClosed(streamer);
|
||||
|
||||
public void /*ITCPStreamerContentConsumer.*/ OnError(TCPStreamer streamer, Exception ex) => this._contentConsumer?.OnError(streamer, ex);
|
||||
|
||||
// Called by SslStream.Read expecting encrypted content
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"Read({offset}, {count})", this._context);
|
||||
|
||||
PullContentFromStreamer();
|
||||
|
||||
int sumReadCount = 0;
|
||||
|
||||
while (this._currentReadSegment == BufferSegment.Empty && this._segmentsToReadFrom.Count == 0)
|
||||
{
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"WaitOne() for new data!", this._context);
|
||||
|
||||
if (this.Length == 0)
|
||||
this._are.WaitOne();
|
||||
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"WaitOne() returned!", this._context);
|
||||
}
|
||||
|
||||
while ((this._currentReadSegment != BufferSegment.Empty || this._segmentsToReadFrom.Count > 0) && count > 0)
|
||||
{
|
||||
if (this._currentReadSegment != BufferSegment.Empty)
|
||||
{
|
||||
int readCount = Math.Min(count, this._currentReadSegment.Count);
|
||||
Array.Copy(this._currentReadSegment.Data, this._currentReadSegment.Offset, buffer, offset, readCount);
|
||||
offset += readCount;
|
||||
count -= readCount;
|
||||
sumReadCount += readCount;
|
||||
|
||||
if (this._currentReadSegment.Count <= readCount)
|
||||
this._currentReadSegment = BufferSegment.Empty;
|
||||
else
|
||||
this._currentReadSegment = this._currentReadSegment.Slice(this._currentReadSegment.Offset + readCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
this._currentReadSegment = this._segmentsToReadFrom.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
this._length -= sumReadCount;
|
||||
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"Read() returns with readCount: {sumReadCount}, remaining: {this._length}", this._context);
|
||||
|
||||
return sumReadCount;
|
||||
}
|
||||
|
||||
// Called by SslStream.Write with encrypted payload
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Verbose(nameof(FrameworkTLSByteForwarder), $"Write({buffer.AsBuffer(offset, count)})", this._context);
|
||||
|
||||
var queued = BufferPool.Get(count, true, this._context);
|
||||
|
||||
Array.Copy(buffer, offset, queued, 0, count);
|
||||
|
||||
this._streamer.EnqueueToSend(queued.AsBuffer(count));
|
||||
}
|
||||
|
||||
public override void Flush() { }
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
|
||||
public override void SetLength(long value) => throw new NotImplementedException();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
this._are?.Dispose();
|
||||
this._are = null;
|
||||
|
||||
this._rws?.Dispose();
|
||||
this._rws = null;
|
||||
|
||||
this._streamer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c78a502070c09ea489f93f800efb2e28
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 267636
|
||||
packageName: Best HTTP
|
||||
packageVersion: 3.0.17
|
||||
assetPath: Packages/com.tivadar.best.http/Runtime/Shared/PlatformSupport/Network/Tcp/Streams/FrameworkTLSByteForwarder.cs
|
||||
uploadId: 783279
|
||||
@ -0,0 +1,297 @@
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
using System;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
|
||||
using Best.HTTP.Hosts.Connections;
|
||||
using Best.HTTP.Hosts.Settings;
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.Logger;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
using Best.HTTP.Shared.Streams;
|
||||
|
||||
namespace Best.HTTP.Shared.PlatformSupport.Network.Tcp.Streams
|
||||
{
|
||||
/*
|
||||
* --> FrameworkTLSStream.Write => SslStream.Write => TLSByteForwarder.Write => TCPStream.EnqueueToSend
|
||||
*
|
||||
* --> TLSByteForwarder.OnContent => SslStream.Read => FrameworkTLSStream.Read
|
||||
* */
|
||||
public sealed class FrameworkTLSStream : PeekableContentProviderStream, ITCPStreamerContentConsumer
|
||||
{
|
||||
public Action<FrameworkTLSStream, TCPStreamer, string /*negotiated appplication protocol*/, Exception> OnNegotiated;
|
||||
public long MaxBufferSize { get => Volatile.Read(ref this._maxBufferSize); set => Interlocked.Exchange(ref this._maxBufferSize, value); }
|
||||
private long _maxBufferSize;
|
||||
|
||||
private string _targetHost;
|
||||
private TCPStreamer _streamer;
|
||||
private FrameworkTLSByteForwarder _forwarder;
|
||||
private SslStream _sslStream;
|
||||
private LoggingContext _context;
|
||||
private HostSettings _hostSettings;
|
||||
|
||||
private int peek_listIdx;
|
||||
private int peek_pos;
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
private static bool loggedWarning = false;
|
||||
#endif
|
||||
private object locker = new object();
|
||||
|
||||
public FrameworkTLSStream(TCPStreamer streamer, string targetHost, HostSettings hostSettings)
|
||||
{
|
||||
this._streamer = streamer;
|
||||
this._targetHost = targetHost;
|
||||
this._context = new LoggingContext(this);
|
||||
this._context.Add("streamer", this._streamer.Context);
|
||||
|
||||
this._hostSettings = hostSettings;
|
||||
this._maxBufferSize = hostSettings.LowLevelConnectionSettings.ReadBufferSize;
|
||||
|
||||
this._forwarder = new FrameworkTLSByteForwarder(this._streamer, this, this.MaxBufferSize, this._context);
|
||||
this._sslStream = new SslStream(this._forwarder,
|
||||
leaveInnerStreamOpen: false,
|
||||
OnUserCertificationValidation,
|
||||
OnUserCertificationSelection,
|
||||
EncryptionPolicy.RequireEncryption);
|
||||
|
||||
this._sslStream.BeginAuthenticateAsClient(targetHost,
|
||||
null,
|
||||
this._hostSettings.TLSSettings.FrameworkTLSSettings.TlsVersions,
|
||||
true,
|
||||
OnAuthenticatedAsClient,
|
||||
null);
|
||||
}
|
||||
|
||||
private bool OnUserCertificationValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(FrameworkTLSStream), $"{nameof(OnUserCertificationValidation)}({sender}, {certificate}, {chain}, {sslPolicyErrors})", this._context);
|
||||
|
||||
var validator = this._hostSettings.TLSSettings.FrameworkTLSSettings.CertificationValidator;
|
||||
if (validator == null)
|
||||
return FrameworkTLSSettings.DefaultCertificationValidator(_targetHost, certificate, chain, sslPolicyErrors);
|
||||
|
||||
return validator(this._targetHost, certificate, chain, sslPolicyErrors);
|
||||
}
|
||||
|
||||
private X509Certificate OnUserCertificationSelection(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(FrameworkTLSStream), $"{nameof(OnUserCertificationSelection)}({sender}, {targetHost}, {localCertificates}, {remoteCertificate}, {acceptableIssuers?.Length})", this._context);
|
||||
|
||||
return this._hostSettings.TLSSettings.FrameworkTLSSettings.ClientCertificationProvider?.Invoke(targetHost, localCertificates, remoteCertificate, acceptableIssuers);
|
||||
}
|
||||
|
||||
private void OnAuthenticatedAsClient(IAsyncResult ar)
|
||||
{
|
||||
HTTPManager.Logger.Information(nameof(FrameworkTLSStream), $"{nameof(OnAuthenticatedAsClient)}()", this._context);
|
||||
|
||||
try
|
||||
{
|
||||
this._sslStream.EndAuthenticateAsClient(ar);
|
||||
|
||||
string alpn = string.Empty;
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
try
|
||||
{
|
||||
alpn = this._sslStream.NegotiatedApplicationProtocol.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Exception(nameof(FrameworkTLSStream), $"{nameof(OnAuthenticatedAsClient)}() - NegotiatedApplicationProtocol", ex, this._context);
|
||||
|
||||
if (!loggedWarning)
|
||||
{
|
||||
loggedWarning = true;
|
||||
HTTPManager.Logger.Warning(nameof(FrameworkTLSStream), $"{nameof(OnAuthenticatedAsClient)}(): SslStream's NegotiatedApplicationProtocol inaccessible! Using http/1.", this._context);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (string.IsNullOrEmpty(alpn))
|
||||
alpn = HTTPProtocolFactory.W3C_HTTP1;
|
||||
|
||||
CallOnNegotiated(alpn, null);
|
||||
|
||||
BeginRead();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception(nameof(FrameworkTLSStream), $"{nameof(OnAuthenticatedAsClient)}()", ex, this._context);
|
||||
|
||||
CallOnNegotiated(null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
bool CallOnNegotiated(string alpn, Exception error)
|
||||
{
|
||||
HTTPManager.Logger.Verbose(nameof(FrameworkTLSStream), $"CallOnNegotiated(\"{alpn}\", {error})", this._context);
|
||||
|
||||
var callback = Interlocked.CompareExchange(ref this.OnNegotiated, null, this.OnNegotiated);
|
||||
if (callback != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(this, this._streamer, alpn, error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception(nameof(FrameworkTLSStream), "OnContent - OnNegotiated", ex, this._streamer.Context);
|
||||
}
|
||||
}
|
||||
|
||||
return callback != null;
|
||||
}
|
||||
|
||||
public override void BeginPeek()
|
||||
{
|
||||
peek_listIdx = 0;
|
||||
peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
|
||||
}
|
||||
|
||||
public override int PeekByte()
|
||||
{
|
||||
if (base.bufferList.Count == 0)
|
||||
return -1;
|
||||
|
||||
var segment = base.bufferList[this.peek_listIdx];
|
||||
if (peek_pos >= segment.Offset + segment.Count)
|
||||
{
|
||||
if (base.bufferList.Count <= this.peek_listIdx + 1)
|
||||
return -1;
|
||||
|
||||
segment = base.bufferList[++this.peek_listIdx];
|
||||
this.peek_pos = segment.Offset;
|
||||
}
|
||||
|
||||
return segment.Data[this.peek_pos++];
|
||||
}
|
||||
|
||||
public void OnContent(TCPStreamer streamer)
|
||||
{
|
||||
if (this._sslStream.IsAuthenticated)
|
||||
BeginRead();
|
||||
}
|
||||
|
||||
int _reading;
|
||||
private void BeginRead()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _reading, 1, 0) != 0)
|
||||
{
|
||||
//HTTPManager.Logger.Warning(nameof(FrameworkTLSStream), $"{nameof(BeginRead)}() - already reading!", this._context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Verbose(nameof(FrameworkTLSStream), $"{nameof(BeginRead)}()", this._context);
|
||||
|
||||
var buffer = BufferPool.Get(Math.Min(this.MaxBufferSize, 1 * 1024 * 1024), true, this._context);
|
||||
|
||||
this._sslStream.ReadAsync(buffer, 0, buffer.Length)
|
||||
//.AsTask()
|
||||
.ContinueWith((ti) =>
|
||||
{
|
||||
int readCount = 0;
|
||||
try
|
||||
{
|
||||
readCount = ti.Result;
|
||||
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Verbose(nameof(FrameworkTLSStream), $"{nameof(OnRead)}({readCount}, {this.Length})", this._context);
|
||||
|
||||
if (readCount > 0)
|
||||
{
|
||||
lock (locker)
|
||||
base.Write(buffer.AsBuffer(readCount));
|
||||
}
|
||||
|
||||
this.Consumer?.OnContent();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _reading, 0);
|
||||
|
||||
if (readCount > 0)
|
||||
BeginRead();
|
||||
}
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/*IAsyncResult ar = null;
|
||||
try
|
||||
{
|
||||
do
|
||||
{
|
||||
var buffer = BufferPool.Get(Math.Min(this.MaxBufferSize, 1 * 1024 * 1024), true, this._context);
|
||||
|
||||
ar = this._sslStream.BeginRead(buffer, 0, buffer.Length, OnRead, buffer);
|
||||
} while (ar != null && ar.CompletedSynchronously);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _reading, 0);
|
||||
|
||||
//if (ar is not null && ar.CompletedSynchronously)
|
||||
// BeginRead();
|
||||
}*/
|
||||
}
|
||||
|
||||
private void OnRead(IAsyncResult ar)
|
||||
{
|
||||
try
|
||||
{
|
||||
var readCount = this._sslStream.EndRead(ar);
|
||||
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Verbose(nameof(FrameworkTLSStream), $"{nameof(OnRead)}({readCount}, {ar.CompletedSynchronously})", this._context);
|
||||
|
||||
if (readCount > 0)
|
||||
{
|
||||
var buffer = ar.AsyncState as byte[];
|
||||
lock (locker)
|
||||
base.Write(buffer.AsBuffer(readCount));
|
||||
|
||||
this.Consumer?.OnContent();
|
||||
}
|
||||
|
||||
// This call might fail if the read completed synchronously.
|
||||
BeginRead();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception(nameof(FrameworkTLSStream), $"EndRead", ex, this._context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void OnConnectionClosed(TCPStreamer streamer) => this.Consumer?.OnConnectionClosed();
|
||||
|
||||
public void OnError(TCPStreamer streamer, Exception ex) => this.Consumer?.OnError(ex);
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) { lock (locker) return base.Read(buffer, offset, count); }
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) => this._sslStream.Write(buffer, offset, count);
|
||||
|
||||
public override void Write(BufferSegment bufferSegment)
|
||||
{
|
||||
using var _ = new AutoReleaseBuffer(bufferSegment);
|
||||
this.Write(bufferSegment.Data, bufferSegment.Offset, bufferSegment.Count);
|
||||
}
|
||||
|
||||
public override void Flush() => this._sslStream.Flush();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
this._sslStream?.Dispose();
|
||||
this._sslStream = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a8f61b94735e634da61bc8b1324b56f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 267636
|
||||
packageName: Best HTTP
|
||||
packageVersion: 3.0.17
|
||||
assetPath: Packages/com.tivadar.best.http/Runtime/Shared/PlatformSupport/Network/Tcp/Streams/FrameworkTLSStream.cs
|
||||
uploadId: 783279
|
||||
@ -0,0 +1,300 @@
|
||||
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||||
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Tls;
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
using Best.HTTP.Shared.Streams;
|
||||
using Best.HTTP.Shared.TLS;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Best.HTTP.Shared.PlatformSupport.Network.Tcp.Streams
|
||||
{
|
||||
public sealed class NonblockingBCTLSStream : PeekableContentProviderStream, ITCPStreamerContentConsumer
|
||||
{
|
||||
public Action<NonblockingBCTLSStream, TCPStreamer, AbstractTls13Client, Exception> OnNegotiated;
|
||||
|
||||
public long MaxBufferSize { get => Volatile.Read(ref this._maxBufferSize); set => Interlocked.Exchange(ref this._maxBufferSize, value); }
|
||||
private long _maxBufferSize;
|
||||
|
||||
private TlsClientProtocol _tlsClientProtocol;
|
||||
private AbstractTls13Client _tlsClient;
|
||||
|
||||
//private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
||||
|
||||
private object locker = new object();
|
||||
private TCPStreamer _streamer;
|
||||
private uint _sendBufferSize;
|
||||
private bool _disposeStreamer;
|
||||
|
||||
private int peek_listIdx;
|
||||
private int peek_pos;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public NonblockingBCTLSStream(TCPStreamer streamer, TlsClientProtocol tlsClientProtocol, AbstractTls13Client tlsClient, bool disposeStreamer, uint maxBufferSize)
|
||||
{
|
||||
this._streamer = streamer;
|
||||
this._streamer.ContentConsumer = this;
|
||||
this._disposeStreamer = disposeStreamer;
|
||||
|
||||
// Maximize buffer use
|
||||
this._sendBufferSize = this._streamer.MaxBufferedWriteAmount;
|
||||
|
||||
this._tlsClientProtocol = tlsClientProtocol;
|
||||
this._tlsClient = tlsClient;
|
||||
|
||||
this.Write(null, 0, 0);
|
||||
|
||||
if (streamer.IsConnectionClosed)
|
||||
CallOnNegotiated(new Exception("Connection closed before TLS negotiation started!"));
|
||||
|
||||
this._maxBufferSize = maxBufferSize;
|
||||
}
|
||||
|
||||
public override void BeginPeek()
|
||||
{
|
||||
lock (this.locker)
|
||||
{
|
||||
peek_listIdx = 0;
|
||||
peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override int PeekByte()
|
||||
{
|
||||
lock (this.locker)
|
||||
{
|
||||
if (base.bufferList.Count == 0)
|
||||
return -1;
|
||||
|
||||
var segment = base.bufferList[this.peek_listIdx];
|
||||
if (peek_pos >= segment.Offset + segment.Count)
|
||||
{
|
||||
if (base.bufferList.Count <= this.peek_listIdx + 1)
|
||||
return -1;
|
||||
|
||||
segment = base.bufferList[++this.peek_listIdx];
|
||||
this.peek_pos = segment.Offset;
|
||||
}
|
||||
|
||||
return segment.Data[this.peek_pos++];
|
||||
}
|
||||
}
|
||||
|
||||
// Called when content from the server is available
|
||||
public void OnContent(TCPStreamer streamer)
|
||||
{
|
||||
lock (this.locker)
|
||||
{
|
||||
var socket = this._streamer?.Socket;
|
||||
|
||||
// Ignore content after a TLS client protocol closure (it can happen because of an error, but the server still pumping data to the client).
|
||||
if (this._disposed || socket == null || this._tlsClientProtocol.IsClosed)
|
||||
{
|
||||
if (this._tlsClientProtocol.IsHandshaking)
|
||||
CallOnNegotiated(new Exception("Connection closed while TLS negotiation is in progress!"));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PullContentFromStreamer();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!CallOnNegotiated(ex))
|
||||
this.Consumer?.OnError(ex);
|
||||
}
|
||||
|
||||
// There's no read/write when it's still hanshaking, so we have to simulate one.
|
||||
if (this._tlsClientProtocol.IsHandshaking)
|
||||
{
|
||||
this.Write(null, 0, 0);
|
||||
return;
|
||||
}
|
||||
else
|
||||
CallOnNegotiated(null);
|
||||
|
||||
// Call OnContent only if we have something to offer.
|
||||
if (this.Length > 0)
|
||||
this.Consumer?.OnContent();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnConnectionClosed(TCPStreamer streamer)
|
||||
{
|
||||
var consumer = this.Consumer;
|
||||
if (consumer != null)
|
||||
consumer.OnConnectionClosed();
|
||||
else
|
||||
CallOnNegotiated(new Exception("TCP Connection closed during TLS negotiation!"));
|
||||
}
|
||||
|
||||
public void OnError(TCPStreamer streamer, Exception ex)
|
||||
{
|
||||
var consumer = this.Consumer;
|
||||
if (consumer != null)
|
||||
consumer.OnError(ex);
|
||||
else
|
||||
CallOnNegotiated(ex);
|
||||
}
|
||||
|
||||
// TODO: It can throw an exception (for example in case of a bad record mac :/), we have to
|
||||
// 1.) handle it
|
||||
// 2.) report to the consumer (through an OnError call)
|
||||
// 3.) prevent other read/write attempts.
|
||||
private void PullContentFromStreamer()
|
||||
{
|
||||
while (!this._disposed && this._streamer.Length > 0 && this._length < this.MaxBufferSize)
|
||||
{
|
||||
var tmp = this._streamer.DequeueReceived();
|
||||
|
||||
if (tmp.Count <= 0)
|
||||
{
|
||||
BufferPool.Release(tmp);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this._tlsClientProtocol.OfferInput(tmp.Data, tmp.Offset, tmp.Count);
|
||||
|
||||
// each call of OfferInput might generate data (for example alerts) to send to the remote peer!
|
||||
this.Write(null, 0, 0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
BufferPool.Release(tmp);
|
||||
|
||||
// each call of OfferInput might generate data (for example alerts) to send to the remote peer!
|
||||
this.Write(null, 0, 0);
|
||||
CallOnNegotiated(ex);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
int available = this._tlsClientProtocol.GetAvailableInputBytes();
|
||||
byte[] readBuffer = tmp.Data;
|
||||
while (available > 0)
|
||||
{
|
||||
if (readBuffer == null)
|
||||
readBuffer = BufferPool.Get(available, true, this._streamer.Context);
|
||||
var readCount = this._tlsClientProtocol.ReadInput(readBuffer, 0, readBuffer.Length);
|
||||
|
||||
base.Write(readBuffer.AsBuffer(readCount));
|
||||
readBuffer = null;
|
||||
|
||||
available = this._tlsClientProtocol.GetAvailableInputBytes();
|
||||
}
|
||||
|
||||
BufferPool.Release(readBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
lock (this.locker)
|
||||
{
|
||||
var readCount = base.Read(buffer, offset, count);
|
||||
|
||||
// pull content from the streamer, if buffered amount is less then the desired.
|
||||
if (base.Length <= this.MaxBufferSize)
|
||||
{
|
||||
try
|
||||
{
|
||||
PullContentFromStreamer();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Consumer.OnError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return readCount;
|
||||
}
|
||||
}
|
||||
|
||||
// write -> tls encoding -> TCP streamer
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
lock (this.locker)
|
||||
{
|
||||
var streamer = this._streamer;
|
||||
if (streamer == null)
|
||||
return;
|
||||
|
||||
if (buffer != null && count > 0)
|
||||
this._tlsClientProtocol.WriteApplicationData(buffer, offset, count);
|
||||
|
||||
int available = 0;
|
||||
available = this._tlsClientProtocol.GetAvailableOutputBytes();
|
||||
while (available > 0)
|
||||
{
|
||||
var tmp = BufferPool.Get(Math.Min(available, this._sendBufferSize), true, streamer.Context);
|
||||
int readCount = 0;
|
||||
|
||||
try
|
||||
{
|
||||
readCount = this._tlsClientProtocol.ReadOutput(tmp, 0, tmp.Length);
|
||||
}
|
||||
catch
|
||||
{
|
||||
BufferPool.Release(tmp);
|
||||
throw;
|
||||
}
|
||||
|
||||
streamer.EnqueueToSend(tmp.AsBuffer(readCount));
|
||||
|
||||
available = this._tlsClientProtocol.GetAvailableOutputBytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(BufferSegment bufferSegment)
|
||||
{
|
||||
lock (this.locker)
|
||||
{
|
||||
using var _ = new AutoReleaseBuffer(bufferSegment);
|
||||
Write(bufferSegment.Data, bufferSegment.Offset, bufferSegment.Count);
|
||||
}
|
||||
}
|
||||
|
||||
bool CallOnNegotiated(Exception error)
|
||||
{
|
||||
var callback = Interlocked.CompareExchange(ref this.OnNegotiated, null, this.OnNegotiated);
|
||||
if (callback != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(this, this._streamer, this._tlsClient, error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception(nameof(NonblockingBCTLSStream), "CallOnNegotiated", ex, this._streamer.Context);
|
||||
}
|
||||
}
|
||||
|
||||
return callback != null;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (this._disposed)
|
||||
return;
|
||||
|
||||
HTTPManager.Logger.Verbose(nameof(NonblockingBCTLSStream), "Dispose", this._streamer.Context);
|
||||
|
||||
this._disposed = true;
|
||||
this._tlsClientProtocol?.Close();
|
||||
|
||||
if (this._disposeStreamer)
|
||||
this._streamer?.Dispose();
|
||||
this._streamer = null;
|
||||
//this._rwLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db49d73dab50cad4a92350516ec20d8d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 267636
|
||||
packageName: Best HTTP
|
||||
packageVersion: 3.0.17
|
||||
assetPath: Packages/com.tivadar.best.http/Runtime/Shared/PlatformSupport/Network/Tcp/Streams/NonblockingBCTLSStream.cs
|
||||
uploadId: 783279
|
||||
@ -0,0 +1,153 @@
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
using Best.HTTP.Shared.Streams;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Best.HTTP.Shared.PlatformSupport.Network.Tcp.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// A non-blocking-read stream over a TCPStreamer that buffers the received bytes from the network in a Peekable stream.
|
||||
/// </summary>
|
||||
public sealed class NonblockingTCPStream : PeekableContentProviderStream, ITCPStreamerContentConsumer
|
||||
{
|
||||
public long MaxBufferSize { get => Volatile.Read(ref this._maxBufferSize); set => Interlocked.Exchange(ref this._maxBufferSize, value); }
|
||||
private long _maxBufferSize;
|
||||
|
||||
private TCPStreamer _streamer;
|
||||
private bool _disposeStreamer;
|
||||
|
||||
private int peek_listIdx;
|
||||
private int peek_pos;
|
||||
|
||||
private object _locker = new object();
|
||||
|
||||
public NonblockingTCPStream(TCPStreamer streamer, bool disposeStreamer, uint maxBufferSize)
|
||||
{
|
||||
this._streamer = streamer;
|
||||
this._streamer.ContentConsumer = this;
|
||||
this._disposeStreamer = disposeStreamer;
|
||||
this._maxBufferSize = maxBufferSize;
|
||||
}
|
||||
|
||||
public override void BeginPeek()
|
||||
{
|
||||
lock (this._locker)
|
||||
{
|
||||
peek_listIdx = 0;
|
||||
peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override int PeekByte()
|
||||
{
|
||||
lock (this._locker)
|
||||
{
|
||||
if (base.bufferList.Count == 0)
|
||||
return -1;
|
||||
|
||||
var segment = base.bufferList[this.peek_listIdx];
|
||||
if (peek_pos >= segment.Offset + segment.Count)
|
||||
{
|
||||
if (base.bufferList.Count <= this.peek_listIdx + 1)
|
||||
return -1;
|
||||
|
||||
segment = base.bufferList[++this.peek_listIdx];
|
||||
this.peek_pos = segment.Offset;
|
||||
}
|
||||
|
||||
return segment.Data[this.peek_pos++];
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
lock (this._locker)
|
||||
{
|
||||
if (this._streamer != null)
|
||||
this._streamer.ContentConsumer = null;
|
||||
|
||||
if (this._disposeStreamer)
|
||||
this._streamer?.Dispose();
|
||||
this._streamer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// PeekableStream's default implementation of write would place the buffer into its inner segment list,
|
||||
// but here we want to send it to the server instead.
|
||||
public override void Write(byte[] buffer, int offset, int count) => this._streamer.EnqueueToSend(buffer.CopyAsBuffer(offset, count));
|
||||
|
||||
public override void Write(BufferSegment buffer) => this._streamer.EnqueueToSend(buffer);
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
lock (this._locker)
|
||||
{
|
||||
int readCount = base.Read(buffer, offset, count);
|
||||
|
||||
// pull content from the streamer, if buffered amount is less then the desired.
|
||||
if (base.Length <= this.MaxBufferSize)
|
||||
{
|
||||
DequeueFromStreamer();
|
||||
this._streamer.BeginReceive();
|
||||
}
|
||||
|
||||
return readCount;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnContent(TCPStreamer streamer)
|
||||
{
|
||||
lock (this._locker)
|
||||
{
|
||||
DequeueFromStreamer();
|
||||
|
||||
var consumer = this.Consumer;
|
||||
if (consumer != null)
|
||||
consumer?.OnContent();
|
||||
else
|
||||
HTTPManager.Logger.Error(nameof(NonblockingTCPStream), $"{nameof(OnContent)}({streamer}) - No consumer to call OnContent on!", streamer.Context);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnConnectionClosed(TCPStreamer streamer)
|
||||
{
|
||||
var consumer = this.Consumer;
|
||||
|
||||
if (consumer != null)
|
||||
consumer?.OnConnectionClosed();
|
||||
else
|
||||
HTTPManager.Logger.Error(nameof(NonblockingTCPStream), $"{nameof(OnConnectionClosed)}({streamer}) - No consumer to call OnConnectionClosed on!", streamer.Context);
|
||||
}
|
||||
|
||||
public void OnError(TCPStreamer streamer, Exception ex)
|
||||
{
|
||||
var consumer = this.Consumer;
|
||||
if (consumer != null)
|
||||
consumer?.OnError(ex);
|
||||
else
|
||||
HTTPManager.Logger.Error(nameof(NonblockingTCPStream), $"{nameof(OnError)}({streamer}, {ex}) - No consumer to call OnError on!", streamer.Context);
|
||||
}
|
||||
|
||||
void DequeueFromStreamer()
|
||||
{
|
||||
if (this._streamer == null)
|
||||
return;
|
||||
|
||||
while (this._streamer.Length > 0 && this._length < this.MaxBufferSize)
|
||||
{
|
||||
var segment = this._streamer.DequeueReceived();
|
||||
|
||||
if (segment.Count <= 0)
|
||||
return;
|
||||
|
||||
base.Write(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4c36b588df49d147981ba1e73401b7e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 267636
|
||||
packageName: Best HTTP
|
||||
packageVersion: 3.0.17
|
||||
assetPath: Packages/com.tivadar.best.http/Runtime/Shared/PlatformSupport/Network/Tcp/Streams/NonblockingTCPStream.cs
|
||||
uploadId: 783279
|
||||
@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.Streams;
|
||||
using Best.HTTP.Shared.Logger;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
|
||||
namespace Best.HTTP.Shared.PlatformSupport.Network.Tcp.Streams
|
||||
{
|
||||
public sealed class NonblockingUnderlyingStream : PeekableContentProviderStream
|
||||
{
|
||||
private System.IO.Stream _stream;
|
||||
private int _receiving;
|
||||
private uint _maxBufferSize;
|
||||
|
||||
private LoggingContext _context;
|
||||
|
||||
private object _locker = new object();
|
||||
private int peek_listIdx;
|
||||
private int peek_pos;
|
||||
|
||||
public NonblockingUnderlyingStream(System.IO.Stream stream, uint maxBufferSize, LoggingContext context)
|
||||
{
|
||||
this._stream = stream;
|
||||
this._context = context;
|
||||
this._maxBufferSize = maxBufferSize;
|
||||
|
||||
if (!stream.CanRead)
|
||||
throw new NotSupportedException("Stream.Read");
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
lock (this._locker)
|
||||
{
|
||||
int readCount = base.Read(buffer, offset, count);
|
||||
|
||||
if (base.Length <= this._maxBufferSize)
|
||||
BeginReceive();
|
||||
|
||||
return readCount;
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginReceive()
|
||||
{
|
||||
if (base._length < this._maxBufferSize && Interlocked.CompareExchange(ref this._receiving, 1, 0) == 0 && this._stream.CanRead)
|
||||
{
|
||||
long readCount = this._maxBufferSize - base._length;
|
||||
var readBuffer = BufferPool.Get(readCount, true, this._context);
|
||||
|
||||
try
|
||||
{
|
||||
var ar = this._stream.BeginRead(readBuffer, 0, (int)readCount, OnReceived, readBuffer);
|
||||
|
||||
if (ar.CompletedSynchronously)
|
||||
HTTPManager.Logger.Warning(nameof(NonblockingUnderlyingStream), $"CompletedSynchronously!", this._context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Exception(nameof(NonblockingUnderlyingStream), $"{nameof(this._stream.BeginRead)}", ex, this._context);
|
||||
|
||||
BufferPool.Release(Interlocked.Exchange(ref readBuffer, null));
|
||||
|
||||
this.Consumer.OnError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReceived(IAsyncResult ar)
|
||||
{
|
||||
int readCount = 0;
|
||||
bool isClosed = true;
|
||||
var readBuffer = ar.AsyncState as byte[];
|
||||
|
||||
try
|
||||
{
|
||||
readCount = this._stream.EndRead(ar);
|
||||
isClosed = readCount <= 0;
|
||||
|
||||
if (!isClosed)
|
||||
{
|
||||
lock (this._locker)
|
||||
base.Write(readBuffer.AsBuffer(0, readCount));
|
||||
|
||||
try
|
||||
{
|
||||
this.Consumer?.OnContent();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
HTTPManager.Logger.Exception(nameof(NonblockingUnderlyingStream), "ContentConsumer.OnContent", e, this._context);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (HTTPManager.Logger.IsDiagnostic)
|
||||
HTTPManager.Logger.Exception(nameof(NonblockingUnderlyingStream), $"{nameof(OnReceived)}", ex, this._context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!isClosed)
|
||||
{
|
||||
Interlocked.Exchange(ref this._receiving, 0);
|
||||
BeginReceive();
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferPool.Release(readBuffer);
|
||||
|
||||
try
|
||||
{
|
||||
this.Consumer?.OnConnectionClosed();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
HTTPManager.Logger.Exception(nameof(NonblockingUnderlyingStream), "Consumer.OnConnectionClosed", e, this._context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void BeginPeek()
|
||||
{
|
||||
lock (this._locker)
|
||||
{
|
||||
peek_listIdx = 0;
|
||||
peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override int PeekByte()
|
||||
{
|
||||
lock (this._locker)
|
||||
{
|
||||
if (base.bufferList.Count == 0)
|
||||
return -1;
|
||||
|
||||
var segment = base.bufferList[this.peek_listIdx];
|
||||
if (peek_pos >= segment.Offset + segment.Count)
|
||||
{
|
||||
if (base.bufferList.Count <= this.peek_listIdx + 1)
|
||||
return -1;
|
||||
|
||||
segment = base.bufferList[++this.peek_listIdx];
|
||||
this.peek_pos = segment.Offset;
|
||||
}
|
||||
|
||||
return segment.Data[this.peek_pos++];
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
this._stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: efbc3c9e712aef64b8f0de50c6fa0ad5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 267636
|
||||
packageName: Best HTTP
|
||||
packageVersion: 3.0.17
|
||||
assetPath: Packages/com.tivadar.best.http/Runtime/Shared/PlatformSupport/Network/Tcp/Streams/NonblockingUnderlyingStream.cs
|
||||
uploadId: 783279
|
||||
Reference in New Issue
Block a user