234 lines
9.2 KiB
C#
234 lines
9.2 KiB
C#
#if !UNITY_WEBGL || UNITY_EDITOR
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
|
|
using Best.HTTP.HostSetting;
|
|
using Best.HTTP.Shared.Extensions;
|
|
using Best.HTTP.Shared.PlatformSupport.Memory;
|
|
using Best.HTTP.Shared.PlatformSupport.Network.Tcp;
|
|
using Best.HTTP.Shared.Streams;
|
|
|
|
using Best.MQTT.Packets;
|
|
|
|
using static Best.HTTP.Shared.HTTPManager;
|
|
|
|
namespace Best.MQTT.Transports
|
|
{
|
|
/// <summary>
|
|
/// Transport to use raw TCP connections with optional secure (TLS) layer use.
|
|
/// </summary>
|
|
public sealed class SecureTCPTransport : Transport, IHeartbeat, INegotiationPeer, IContentConsumer
|
|
{
|
|
public PeekableContentProviderStream ContentProvider { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Queue for sending packets
|
|
/// </summary>
|
|
private ConcurrentQueue<BufferSegment> _buffers = new ConcurrentQueue<BufferSegment>();
|
|
|
|
/// <summary>
|
|
/// Event to signal the sending thread.
|
|
/// </summary>
|
|
private AutoResetEvent _bufferAvailableEvent = new AutoResetEvent(false);
|
|
|
|
private volatile bool closed;
|
|
private volatile int runningThreadCount;
|
|
|
|
private Negotiator _negotiator;
|
|
|
|
public SecureTCPTransport(MQTTClient client)
|
|
:base(client)
|
|
{
|
|
}
|
|
|
|
internal override void BeginConnect(CancellationToken token = default)
|
|
{
|
|
base.BeginConnect(token);
|
|
|
|
if (token.IsCancellationRequested)
|
|
return;
|
|
|
|
if (this.State != TransportStates.Initial)
|
|
throw new Exception($"{nameof(SecureTCPTransport)} couldn't {nameof(BeginConnect)} as it's already in state {this.State}!");
|
|
|
|
Logger.Information(nameof(SecureTCPTransport), $"{nameof(BeginConnect)}", this.Context);
|
|
|
|
var options = this.Parent.Options;
|
|
|
|
NegotiationParameters parameters = new NegotiationParameters();
|
|
parameters.context = this.Context;
|
|
//parameters.tryToKeepAlive = true;
|
|
parameters.proxy = Proxy;
|
|
parameters.createProxyTunel = true;
|
|
parameters.targetUri = new Uri($"tcp://{options.Host}:{options.Port}");
|
|
parameters.negotiateTLS = options.UseTLS;
|
|
//parameters.optionalRequest = null;
|
|
parameters.token = token;
|
|
|
|
parameters.hostSettings = PerHostSettings.Get(HostKey.From(parameters.targetUri, new Best.HTTP.Request.Settings.ProxySettings() { Proxy = parameters.proxy }));
|
|
|
|
this._negotiator = new Negotiator(this, parameters);
|
|
this._negotiator.Start();
|
|
}
|
|
|
|
List<string> INegotiationPeer.GetSupportedProtocolNames(Negotiator negotiator) => new List<string> { Best.HTTP.Hosts.Connections.HTTPProtocolFactory.W3C_HTTP1 };
|
|
|
|
bool INegotiationPeer.MustStopAdvancingToNextStep(Negotiator negotiator, NegotiationSteps finishedStep, NegotiationSteps nextStep, Exception error)
|
|
{
|
|
bool stop = negotiator.Parameters.token.IsCancellationRequested || error != null;
|
|
|
|
if (stop)
|
|
this.transportEvents.Enqueue(new TransportEvent(TransportEventTypes.StateChange, TransportStates.DisconnectedWithError, error?.ToString() ?? "IsCancellationRequested"));
|
|
|
|
return stop;
|
|
}
|
|
|
|
void INegotiationPeer.EvaluateProxyNegotiationFailure(Negotiator negotiator, Exception error, bool resendForAuthentication)
|
|
{
|
|
this.transportEvents.Enqueue(new TransportEvent(TransportEventTypes.StateChange, TransportStates.DisconnectedWithError, error?.ToString() ?? "Proxy authentication failed"));
|
|
}
|
|
|
|
void INegotiationPeer.OnNegotiationFailed(Negotiator negotiator, Exception error)
|
|
{
|
|
this.transportEvents.Enqueue(new TransportEvent(TransportEventTypes.StateChange, TransportStates.DisconnectedWithError, error.ToString()));
|
|
}
|
|
|
|
void INegotiationPeer.OnNegotiationFinished(Negotiator negotiator, PeekableContentProviderStream stream, TCPStreamer streamer, string negotiatedProtocol)
|
|
{
|
|
this.transportEvents.Enqueue(new TransportEvent(TransportEventTypes.StateChange, TransportStates.Connected));
|
|
|
|
//(stream as IPeekableContentProvider).Consumer = this;
|
|
stream.SetTwoWayBinding(this);
|
|
|
|
Best.HTTP.Shared.PlatformSupport.Threading.ThreadedRunner.RunLongLiving(SendThread);
|
|
}
|
|
|
|
internal override void Send(BufferSegment buffer)
|
|
{
|
|
if (this.State != TransportStates.Connected)
|
|
{
|
|
Logger.Warning(nameof(SecureTCPTransport), $"Send called while it's not in the Connected state! State: {this.State}", this.Context);
|
|
return;
|
|
}
|
|
|
|
Logger.Information(nameof(SecureTCPTransport), $"{nameof(Send)}({buffer})", this.Context);
|
|
this._buffers.Enqueue(buffer);
|
|
this._bufferAvailableEvent.Set();
|
|
}
|
|
|
|
internal override void BeginDisconnect()
|
|
{
|
|
if (this.State >= TransportStates.Disconnecting)
|
|
return;
|
|
|
|
ChangeStateTo(TransportStates.Disconnecting, string.Empty);
|
|
try
|
|
{
|
|
this.closed = true;
|
|
this._bufferAvailableEvent.Set();
|
|
}
|
|
catch
|
|
{
|
|
ChangeStateTo(TransportStates.Disconnected, string.Empty);
|
|
}
|
|
}
|
|
|
|
private void SendThread()
|
|
{
|
|
try
|
|
{
|
|
Interlocked.Increment(ref this.runningThreadCount);
|
|
Logger.Information(nameof(SecureTCPTransport), $"{nameof(SendThread)} is up and running! runningThreadCount: {this.runningThreadCount}", this.Context);
|
|
|
|
while (!closed)
|
|
{
|
|
this._bufferAvailableEvent.WaitOne();
|
|
while (this._buffers.TryDequeue(out BufferSegment buff))
|
|
{
|
|
this._negotiator.Stream.Write(buff);
|
|
}
|
|
}
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
Logger.Exception(nameof(SecureTCPTransport), nameof(SendThread), ex, this.Context);
|
|
}
|
|
finally
|
|
{
|
|
Interlocked.Decrement(ref this.runningThreadCount);
|
|
this.transportEvents.Enqueue(new TransportEvent(TransportEventTypes.StateChange, TransportStates.Disconnected));
|
|
}
|
|
}
|
|
|
|
// Call it every time we received a Disconnected event. This means that CleanupAfterDisconnect going to be called 2-3 times.
|
|
protected override void CleanupAfterDisconnect()
|
|
{
|
|
// Run cleanup logic only when both threads are closed
|
|
if (this.runningThreadCount == 0 && this._bufferAvailableEvent != null)
|
|
{
|
|
base.CleanupAfterDisconnect();
|
|
|
|
this._bufferAvailableEvent?.Dispose();
|
|
this._bufferAvailableEvent = null;
|
|
|
|
this._negotiator?.Stream?.Dispose();
|
|
|
|
Logger.Information(nameof(SecureTCPTransport), $"{nameof(CleanupAfterDisconnect)} finished!", this.Context);
|
|
}
|
|
}
|
|
|
|
public void SetBinding(PeekableContentProviderStream contentProvider) => this.ContentProvider = contentProvider;
|
|
public void UnsetBinding() => this.ContentProvider = null;
|
|
|
|
public void OnContent()
|
|
{
|
|
var available = this.ContentProvider.Length;
|
|
var buffer = BufferPool.Get(available, true);
|
|
int count = this.ContentProvider.Read(buffer, 0, (int)available);
|
|
|
|
if (count == 0)
|
|
{
|
|
//this.closed = true;
|
|
//this._bufferAvailableEvent.Set();
|
|
//this.transportEvents.Enqueue(new TransportEvent(TransportEventTypes.StateChange, TransportStates.DisconnectedWithError, "TCP closed"));
|
|
//return;
|
|
//throw new Exception("TCP connection closed unexpectedly!");
|
|
}
|
|
|
|
this.ReceiveStream.Write(new BufferSegment(buffer, 0, count));
|
|
|
|
Logger.Information(nameof(SecureTCPTransport), $"{nameof(OnContent)} - Received ({count})", this.Context);
|
|
|
|
try
|
|
{
|
|
TryParseIncomingPackets();
|
|
}
|
|
catch (MQTTException ex)
|
|
{
|
|
this.transportEvents.Enqueue(new TransportEvent(TransportEventTypes.MQTTException, ex, nameof(TryParseIncomingPackets)));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Exception(nameof(SecureTCPTransport), $"{nameof(OnContent)}.TryParseIncomingPackets", ex, this.Context);
|
|
}
|
|
}
|
|
|
|
public void OnConnectionClosed()
|
|
{
|
|
if (State == TransportStates.Disconnecting)
|
|
this.transportEvents.Enqueue(new TransportEvent(TransportEventTypes.StateChange, TransportStates.Disconnected));
|
|
else
|
|
this.transportEvents.Enqueue(new TransportEvent(TransportEventTypes.StateChange, TransportStates.DisconnectedWithError, "TCP closed by remote peer."));
|
|
}
|
|
|
|
public void OnError(Exception ex)
|
|
{
|
|
this.transportEvents.Enqueue(new TransportEvent(TransportEventTypes.StateChange, TransportStates.DisconnectedWithError, ex.Message));
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|