315 lines
10 KiB
C#
Raw Normal View History

2022-05-11 15:02:17 +02:00
using Capnp.Rpc;
using FabAccessAPI.Exceptions;
using FabAccessAPI.Schema;
2022-05-16 22:41:29 +02:00
using NLog;
2022-05-12 23:08:37 +02:00
using S22.Sasl;
2022-05-10 13:35:23 +02:00
using System;
2022-05-11 15:02:17 +02:00
using System.Collections.Generic;
2022-05-12 23:08:37 +02:00
using System.Linq;
2022-05-11 15:02:17 +02:00
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
2022-05-10 13:35:23 +02:00
namespace FabAccessAPI
{
public class API : IAPI
{
2022-05-16 22:41:29 +02:00
#region Logger
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
#endregion
2022-05-10 13:35:23 +02:00
#region Private Members
2022-05-12 23:08:37 +02:00
private TcpRpcClient _TcpRpcClient;
private IBootstrap _Bootstrap;
2022-05-10 13:35:23 +02:00
#endregion
#region Constructors
public API()
{
}
#endregion
2022-05-10 23:50:04 +02:00
2022-05-10 13:35:23 +02:00
#region Events
public event EventHandler<ConnectionStatusChange> ConnectionStatusChanged;
2022-05-12 23:08:37 +02:00
public void OnTcpRpcConnectionChanged(object sender, ConnectionStateChange args)
{
EventHandler<ConnectionStatusChange> eventHandler = ConnectionStatusChanged;
if (eventHandler == null)
{
return;
}
if (args.LastState == ConnectionState.Initializing && args.NewState == ConnectionState.Active)
{
2022-05-16 22:41:29 +02:00
Log.Trace("TcpRpcClient Event Connected");
2022-05-12 23:08:37 +02:00
eventHandler(this, ConnectionStatusChange.Connected);
}
if (args.LastState == ConnectionState.Active && args.NewState == ConnectionState.Down)
{
2022-05-16 22:41:29 +02:00
Log.Trace("TcpRpcClient Event ConnectionLoss");
2022-05-12 23:08:37 +02:00
eventHandler(this, ConnectionStatusChange.ConnectionLoss);
2022-05-16 22:41:29 +02:00
_TcpRpcClient = null;
2022-05-12 23:08:37 +02:00
}
}
2022-05-10 13:35:23 +02:00
#endregion
#region Members
public ConnectionData ConnectionData { get; private set; }
public ConnectionInfo ConnectionInfo { get; private set; }
public bool IsConnected
{
get
{
2022-05-12 23:08:37 +02:00
return _TcpRpcClient != null && _TcpRpcClient.State == ConnectionState.Active;
2022-05-10 13:35:23 +02:00
}
}
2022-05-12 23:08:37 +02:00
public Session Session { get; private set; }
2022-05-10 13:35:23 +02:00
#endregion
#region Methods
2022-05-11 15:02:17 +02:00
/// <summary>
/// Connect to server with ConnectionData
/// </summary>
/// <exception cref="AuthenticationException"></exception>
/// <exception cref="ConnectingFailedException"></exception>
2022-05-12 23:08:37 +02:00
public async Task Connect(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null)
2022-05-10 13:35:23 +02:00
{
2022-05-11 15:02:17 +02:00
if (IsConnected)
{
await Disconnect();
}
2022-05-12 23:08:37 +02:00
if(tcpRpcClient == null)
{
_TcpRpcClient = new TcpRpcClient();
}
else
{
_TcpRpcClient = tcpRpcClient;
}
2022-05-11 15:02:17 +02:00
try
{
2022-05-12 23:08:37 +02:00
_TcpRpcClient.ConnectionStateChanged += OnTcpRpcConnectionChanged;
await _ConnectAsync(_TcpRpcClient, connectionData).ConfigureAwait(false);
_Bootstrap = _TcpRpcClient.GetMain<IBootstrap>();
ConnectionInfo = await _GetConnectionInfo(_Bootstrap);
Session = await _Authenticate(connectionData).ConfigureAwait(false);
ConnectionData = connectionData;
2022-05-16 22:41:29 +02:00
Log.Info("API connected");
2022-05-11 15:02:17 +02:00
}
2022-05-16 22:41:29 +02:00
catch(System.Exception ex)
2022-05-11 15:02:17 +02:00
{
await Disconnect().ConfigureAwait(false);
2022-05-16 22:41:29 +02:00
Log.Warn(ex, "API connecting failed");
throw ex;
2022-05-11 15:02:17 +02:00
}
2022-05-10 13:35:23 +02:00
}
2022-05-11 15:02:17 +02:00
public Task Disconnect()
2022-05-10 13:35:23 +02:00
{
2022-05-11 15:02:17 +02:00
if (IsConnected)
{
2022-05-12 23:08:37 +02:00
_TcpRpcClient.Dispose();
2022-05-11 15:02:17 +02:00
}
2022-05-12 23:08:37 +02:00
_Bootstrap = null;
Session = null;
_TcpRpcClient = null;
2022-05-11 15:02:17 +02:00
ConnectionData = null;
ConnectionInfo = null;
2022-05-16 16:07:33 +02:00
ConnectionStatusChanged?.Invoke(this, ConnectionStatusChange.Disconnected);
2022-05-12 23:08:37 +02:00
2022-05-16 22:41:29 +02:00
Log.Info("API disconnected");
2022-05-11 15:02:17 +02:00
return Task.CompletedTask;
2022-05-10 13:35:23 +02:00
}
2022-05-12 23:08:37 +02:00
public async Task Reconnect()
2022-05-10 13:35:23 +02:00
{
2022-05-12 23:08:37 +02:00
if (ConnectionData != null)
{
await Connect(ConnectionData);
}
2022-05-16 16:07:33 +02:00
ConnectionStatusChanged?.Invoke(this, ConnectionStatusChange.Reconnected);
2022-05-16 22:41:29 +02:00
Log.Info("API reconnected");
2022-05-10 13:35:23 +02:00
}
2022-05-12 23:08:37 +02:00
public async Task<ConnectionInfo> TestConnection(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null)
2022-05-10 13:35:23 +02:00
{
2022-05-11 15:02:17 +02:00
try
{
2022-05-12 23:08:37 +02:00
if (tcpRpcClient == null)
2022-05-11 15:02:17 +02:00
{
2022-05-12 23:08:37 +02:00
tcpRpcClient = new TcpRpcClient();
2022-05-11 15:02:17 +02:00
}
2022-05-12 23:08:37 +02:00
await _ConnectAsync(tcpRpcClient, connectionData).ConfigureAwait(false);
IBootstrap testBootstrap = tcpRpcClient.GetMain<IBootstrap>();
ConnectionInfo connectionInfo = await _GetConnectionInfo(testBootstrap).ConfigureAwait(false);
tcpRpcClient.Dispose();
return connectionInfo;
2022-05-11 15:02:17 +02:00
}
catch
{
2022-05-12 23:08:37 +02:00
throw new ConnectingFailedException();
2022-05-11 15:02:17 +02:00
}
}
#endregion
#region Private Methods
2022-05-12 23:08:37 +02:00
/// <summary>
/// Validate Certificate
/// TODO: Do some validation
/// </summary>
2022-05-16 16:07:33 +02:00
private static bool _RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
2022-05-11 15:02:17 +02:00
{
// TODO Cert Check
return true;
}
/// <summary>
/// Connect to server async with ConnectionData
/// </summary>
/// <exception cref="AuthenticationException">TLS Error</exception>
/// <exception cref="ConnectingFailedException">Based on RPC Exception</exception>
///
2022-05-12 23:08:37 +02:00
private async Task _ConnectAsync(TcpRpcClient rpcClient, ConnectionData connectionData)
2022-05-11 15:02:17 +02:00
{
rpcClient.InjectMidlayer((tcpstream) =>
{
2022-05-16 16:07:33 +02:00
var sslStream = new SslStream(tcpstream, false, new RemoteCertificateValidationCallback(_RemoteCertificateValidationCallback));
2022-05-11 15:02:17 +02:00
try
{
sslStream.AuthenticateAsClient("bffhd");
return sslStream;
}
catch (AuthenticationException)
{
sslStream.Close();
throw;
}
});
try
{
rpcClient.Connect(connectionData.Host.Host, connectionData.Host.Port);
await rpcClient.WhenConnected.ConfigureAwait(false);
}
catch (RpcException exception) when (string.Equals(exception.Message, "TcpRpcClient is unable to connect", StringComparison.Ordinal))
{
throw new ConnectingFailedException("Connecting failed", exception);
}
}
2022-05-12 23:08:37 +02:00
/// <summary>
/// Create ConnectionInfo from Bootstrap
/// </summary>
private async Task<ConnectionInfo> _GetConnectionInfo(IBootstrap bootstrap)
{
ConnectionInfo connectionInfo = new ConnectionInfo()
{
APIVersion = await bootstrap.GetAPIVersion().ConfigureAwait(false),
Mechanisms = new List<string>(await bootstrap.Mechanisms().ConfigureAwait(false)),
ServerName = (await bootstrap.GetServerRelease().ConfigureAwait(false)).Item1,
ServerRelease = (await bootstrap.GetServerRelease().ConfigureAwait(false)).Item2,
};
return connectionInfo;
}
2022-05-11 15:02:17 +02:00
/// <summary>
/// Authenticate connection with ConnectionData
/// </summary>
/// <exception cref="UnsupportedMechanismException"></exception>
/// <exception cref="InvalidCredentialsException"></exception>
/// <exception cref="AuthenticationFailedException"></exception>
2022-05-12 23:08:37 +02:00
private async Task<Session> _Authenticate(ConnectionData connectionData)
2022-05-11 15:02:17 +02:00
{
2022-05-12 23:08:37 +02:00
IAuthentication? authentication = await _Bootstrap.CreateSession(MechanismString.ToString(connectionData.Mechanism)).ConfigureAwait(false);
2022-05-16 16:07:33 +02:00
return await _SASLAuthenticate(authentication, MechanismString.ToString(connectionData.Mechanism), connectionData.Properties).ConfigureAwait(false);
2022-05-12 23:08:37 +02:00
}
/// <summary>
/// Authenticate Connection to get Session
/// </summary>
/// <exception cref="BadMechanismException"></exception>
/// <exception cref="InvalidCredentialsException"></exception>
/// <exception cref="AuthenticationFailedException"></exception>
2022-05-16 16:07:33 +02:00
private async Task<Session> _SASLAuthenticate(IAuthentication authentication, string mech, Dictionary<string, object> properties)
2022-05-12 23:08:37 +02:00
{
SaslMechanism? saslMechanism = SaslFactory.Create(mech);
foreach (KeyValuePair<string, object> entry in properties)
{
saslMechanism.Properties.Add(entry.Key, entry.Value);
}
byte[] data = new byte[0];
if (saslMechanism.HasInitial)
{
data = saslMechanism.GetResponse(new byte[0]);
}
Response? response = await authentication.Step(data);
while (!saslMechanism.IsCompleted)
{
if(response.Failed != null)
{
break;
}
if(response.Challenge != null)
{
byte[]? additional = saslMechanism.GetResponse(response.Challenge.ToArray());
response = await authentication.Step(additional);
}
else
{
throw new AuthenticationFailedException();
}
}
if (response.Successful != null)
{
return response.Successful.Session;
}
else if (response.Failed != null)
{
switch (response.Failed.Code)
{
case Response.Error.badMechanism:
throw new BadMechanismException();
case Response.Error.invalidCredentials:
throw new InvalidCredentialsException();
case Response.Error.aborted:
case Response.Error.failed:
default:
throw new AuthenticationFailedException(response.Failed.AdditionalData.ToArray());
}
}
else
{
throw new AuthenticationFailedException();
}
2022-05-10 13:35:23 +02:00
}
#endregion
}
}