Files
ChunilENG/Packages/com.tivadar.best.mqtt/Runtime/MQTT/Transports/SecureTCPTransport.cs
정영민 2dd5d814a7 update
2025-02-20 09:59:37 +09:00

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