Added: external libs

This commit is contained in:
TheJoKlLa 2024-02-27 13:47:37 +01:00
parent a4b95106d7
commit 4b9f7688cd
31 changed files with 1006 additions and 9 deletions

15
.gitmodules vendored Normal file
View File

@ -0,0 +1,15 @@
[submodule "external/Shell"]
path = external/Shell
url = https://github.com/AvaloniaInside/Shell.git
[submodule "external/capnproto-dotnetcore_Runtime"]
path = external/capnproto-dotnetcore_Runtime
url = https://github.com/FabInfra/capnproto-dotnetcore_Runtime.git
[submodule "external/S22.Sasl"]
path = external/S22.Sasl
url = https://github.com/kjkriegel/S22.Sasl.git
[submodule "external/nfc"]
path = external/nfc
url = https://gitlab.com/fabinfra/fabaccess/nfc.git
[submodule "api/schema"]
path = api/schema
url = https://gitlab.com/fabinfra/fabaccess/fabaccess-api.git

View File

@ -20,6 +20,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md README.md = README.md
EndProjectSection EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnp.Net.Runtime", "external\capnproto-dotnetcore_Runtime\Capnp.Net.Runtime\Capnp.Net.Runtime.csproj", "{2805B6A8-35FC-4C2E-900A-111223E82985}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "S22.Sasl", "external\S22.Sasl\S22.Sasl.csproj", "{FC22A6DA-322D-4BF2-9288-AAA496B75F36}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvaloniaInside.Shell", "external\Shell\src\AvaloniaInside.Shell\AvaloniaInside.Shell.csproj", "{0D3D5AE2-9524-4387-9565-EA363E8BF608}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NFC", "external\nfc\NFC\NFC.csproj", "{2B29261E-E23F-46A6-AFBC-1738E7F81471}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FabAccessAPI", "api\FabAccessAPI.csproj", "{26B621D8-E409-4ADE-B53C-8888ADA6817C}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -50,6 +60,26 @@ Global
{1E07BDF6-DDC7-462E-BD29-77E150717455}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E07BDF6-DDC7-462E-BD29-77E150717455}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E07BDF6-DDC7-462E-BD29-77E150717455}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E07BDF6-DDC7-462E-BD29-77E150717455}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E07BDF6-DDC7-462E-BD29-77E150717455}.Release|Any CPU.Build.0 = Release|Any CPU {1E07BDF6-DDC7-462E-BD29-77E150717455}.Release|Any CPU.Build.0 = Release|Any CPU
{2805B6A8-35FC-4C2E-900A-111223E82985}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2805B6A8-35FC-4C2E-900A-111223E82985}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2805B6A8-35FC-4C2E-900A-111223E82985}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2805B6A8-35FC-4C2E-900A-111223E82985}.Release|Any CPU.Build.0 = Release|Any CPU
{FC22A6DA-322D-4BF2-9288-AAA496B75F36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC22A6DA-322D-4BF2-9288-AAA496B75F36}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC22A6DA-322D-4BF2-9288-AAA496B75F36}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC22A6DA-322D-4BF2-9288-AAA496B75F36}.Release|Any CPU.Build.0 = Release|Any CPU
{0D3D5AE2-9524-4387-9565-EA363E8BF608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D3D5AE2-9524-4387-9565-EA363E8BF608}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D3D5AE2-9524-4387-9565-EA363E8BF608}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D3D5AE2-9524-4387-9565-EA363E8BF608}.Release|Any CPU.Build.0 = Release|Any CPU
{2B29261E-E23F-46A6-AFBC-1738E7F81471}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B29261E-E23F-46A6-AFBC-1738E7F81471}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B29261E-E23F-46A6-AFBC-1738E7F81471}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B29261E-E23F-46A6-AFBC-1738E7F81471}.Release|Any CPU.Build.0 = Release|Any CPU
{26B621D8-E409-4ADE-B53C-8888ADA6817C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{26B621D8-E409-4ADE-B53C-8888ADA6817C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26B621D8-E409-4ADE-B53C-8888ADA6817C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{26B621D8-E409-4ADE-B53C-8888ADA6817C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0-android</TargetFramework> <TargetFramework>net8.0-android34.0</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ApplicationId>com.CompanyName.AvaloniaTest</ApplicationId> <ApplicationId>com.CompanyName.AvaloniaTest</ApplicationId>

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier> <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<WasmMainJSPath>AppBundle\main.js</WasmMainJSPath> <WasmMainJSPath>AppBundle\main.js</WasmMainJSPath>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>

View File

@ -3,7 +3,7 @@
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects. <!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.
One for Windows with net7.0-windows TFM, one for MacOS with net7.0-macos and one with net7.0 TFM for Linux.--> One for Windows with net7.0-windows TFM, one for MacOS with net7.0-macos and one with net7.0 TFM for Linux.-->
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0-ios</TargetFramework> <TargetFramework>net8.0-ios16.1</TargetFramework>
<SupportedOSPlatformVersion>10.0</SupportedOSPlatformVersion> <SupportedOSPlatformVersion>10.0</SupportedOSPlatformVersion>
<ProvisioningType>manual</ProvisioningType> <ProvisioningType>manual</ProvisioningType>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
@ -12,7 +12,6 @@
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.9" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.9" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.9" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.9" />
<PackageReference Include="AvaloniaInside.Shell" Version="1.1.2" />
<PackageReference Include="Material.Avalonia" Version="3.3.0" /> <PackageReference Include="Material.Avalonia" Version="3.3.0" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.0" /> <PackageReference Include="Material.Icons.Avalonia" Version="2.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
@ -28,4 +27,12 @@
<Folder Include="Themes\" /> <Folder Include="Themes\" />
<Folder Include="Resources\" /> <Folder Include="Resources\" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\api\FabAccessAPI.csproj" />
<ProjectReference Include="..\..\external\capnproto-dotnetcore_Runtime\Capnp.Net.Runtime\Capnp.Net.Runtime.csproj" />
<ProjectReference Include="..\..\external\nfc\NFC\NFC.csproj" />
<ProjectReference Include="..\..\external\S22.Sasl\S22.Sasl.csproj" />
<ProjectReference Include="..\..\external\Shell\src\AvaloniaInside.Shell\AvaloniaInside.Shell.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -59,6 +59,14 @@
<SideMenuItem.Icon> <SideMenuItem.Icon>
</SideMenuItem.Icon> </SideMenuItem.Icon>
</SideMenuItem> </SideMenuItem>
<SideMenuItem Path="/main/settings" Title="Settings">
<SideMenuItem.Icon>
</SideMenuItem.Icon>
</SideMenuItem>
<SideMenuItem Path="/main/profile" Title="Profile">
<SideMenuItem.Icon>
</SideMenuItem.Icon>
</SideMenuItem>
<ShellView.SideMenuHeader> <ShellView.SideMenuHeader>
<TextBlock Text="FabAccess" <TextBlock Text="FabAccess"

View File

@ -12,7 +12,7 @@
ShellView.EnableSafeAreaForBottom="False"> ShellView.EnableSafeAreaForBottom="False">
<TabControl.ItemTemplate> <TabControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid RowDefinitions="*,*" Width="70"> <!--<Grid RowDefinitions="*,*" Width="70">
<i:Icon Value="{Binding Instance.Icon}" <i:Icon Value="{Binding Instance.Icon}"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Height="24" Height="24"
@ -22,7 +22,7 @@
Text="{Binding Path=(NavigationBar.Header)}" Text="{Binding Path=(NavigationBar.Header)}"
FontSize="14" FontSize="14"
HorizontalAlignment="Center"></TextBlock> HorizontalAlignment="Center"></TextBlock>
</Grid> </Grid>-->
</DataTemplate> </DataTemplate>
</TabControl.ItemTemplate> </TabControl.ItemTemplate>
<TabControl.ContentTemplate> <TabControl.ContentTemplate>
@ -37,6 +37,7 @@
</Style> </Style>
<Style Selector="TabControl.MainTab /template/ Border > DockPanel > ItemsPresenter"> <Style Selector="TabControl.MainTab /template/ Border > DockPanel > ItemsPresenter">
<Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Height" Value="0" />
</Style> </Style>
</TabControl.Styles> </TabControl.Styles>
</TabControl> </TabControl>

View File

@ -93,7 +93,7 @@
<Setter Property="Background" Value="#40000000" /> <Setter Property="Background" Value="#40000000" />
<Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="CornerRadius" Value="5" /> <Setter Property="CornerRadius" Value="5" />
<Setter Property="Padding" Value="8" /> <Setter Property="Padding" Value="3" />
</Style> </Style>
<Style Selector="TabControl.Center"> <Style Selector="TabControl.Center">

506
api/API.cs Normal file
View File

@ -0,0 +1,506 @@
using Capnp.Rpc;
using FabAccessAPI.Exceptions;
using FabAccessAPI.Exceptions.SASL;
using FabAccessAPI.Schema;
using NLog;
using S22.Sasl;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
namespace FabAccessAPI
{
public class API : IAPI
{
#region Logger
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
#endregion
#region Private Members
/// <summary>
/// Internal client to connect to a server with TCP and RPC
/// </summary>
private TcpRpcClient _TcpRpcClient;
/// <summary>
/// Private ConnectionData
/// </summary>
private ConnectionData _ConnectionData;
/// <summary>
/// Private ServerData
/// </summary>
private ServerData _ServerData;
/// <summary>
/// Private Session
/// </summary>
private Session _Session;
/// <summary>
/// Private Bootstrap
/// </summary>
private IBootstrap _Bootstrap;
/// <summary>
/// Timer to check connection status
/// </summary>
private readonly Timer _ConnectionHeatbeat;
/// <summary>
/// Semaphore to connect only once
/// </summary>
private static SemaphoreSlim _ConnectSemaphore = new SemaphoreSlim(1, 1);
#endregion
#region Constructors
public API()
{
_ConnectionHeatbeat = new Timer(Heartbeat, null, 1000, 1000);
}
#endregion
#region Members
/// <summary>
/// State of the conneciton, can the API-Service connect to a server
/// </summary>
public bool CanConnect
{
get
{
return _ConnectionData != null;
}
}
/// <summary>
/// State of the conneciton, is the API-Service connecting to a server
/// </summary>
public bool IsConnecting
{
get
{
return _TcpRpcClient != null && _ConnectionData != null;
}
}
/// <summary>
/// State of the conneciton, is the API-Service connected to a server
/// </summary>
public bool IsConnected
{
get
{
return _TcpRpcClient != null && _TcpRpcClient.State == ConnectionState.Active;
}
}
/// <summary>
/// Information about the connection
/// </summary>
/// <exception cref="InvalidOperationException"> When API-Service is not connected or trying to connected to a server </exception>
public ConnectionData ConnectionData
{
get
{
if(_ConnectionData == null || !IsConnecting)
{
throw new InvalidOperationException();
}
else
{
return _ConnectionData;
}
}
private set
{
_ConnectionData = value;
}
}
/// <summary>
/// Information about the server
/// Is only avalible if the API-Service is connected
/// </summary>
/// <exception cref="InvalidOperationException"> When API-Service is not connected </exception>
public ServerData ServerData
{
get
{
if (_ServerData == null || !IsConnected)
{
throw new InvalidOperationException();
}
else
{
return _ServerData;
}
}
private set
{
_ServerData = value;
}
}
#endregion
#region Events
/// <summary>
/// Event on changes in connection status
/// </summary>
public event EventHandler<ConnectionStatusChanged> ConnectionStatusChanged;
/// <summary>
/// Unbind all handlers from EventHandler<ConnectionStatusChanged>
/// </summary>
public void UnbindEventHandler()
{
if (ConnectionStatusChanged != null)
{
Log.Trace("Eventhandlers unbinded");
foreach (Delegate d in ConnectionStatusChanged.GetInvocationList())
{
ConnectionStatusChanged -= (EventHandler<ConnectionStatusChanged>)d;
}
}
}
/// <summary>
/// Eventhandler for TcpRpcConnectionChanged
/// Track connection loss and publish i in ConnectionStatusChanged
/// </summary>
public void OnTcpRpcConnectionChanged(object sender, ConnectionStateChange args)
{
if (args.LastState == ConnectionState.Active && args.NewState == ConnectionState.Down)
{
Log.Trace("TcpRpcClient Event ConnectionLoss");
ConnectionStatusChanged?.Invoke(this, FabAccessAPI.ConnectionStatusChanged.ConnectionLoss);
}
}
#endregion
#region Session
/// <summary>
/// Get session after connection
/// </summary>
/// <exception cref="InvalidOperationException"> When API-Service is not connected </exception>
public Session Session
{
get
{
if (_Session == null || !IsConnected)
{
throw new InvalidOperationException();
}
else
{
return _Session;
}
}
private set
{
_Session = value;
}
}
#endregion
#region Methods
/// <summary>
/// Connect to server with ConnectionData
/// If connection lost, the API-Server will try to reconnect
/// </summary>
/// <param name="connectionData"> Data to establish a connection to a server </param>
/// <exception cref="ConnectionException"> When API-Service can not connect to a server </exception>
/// <exception cref="AuthenticationException"> When API-Service can connect to a server but can not authenticate </exception>
/// <exception cref="InvalidOperationException"> When API-Service is allready connected </exception>
public async Task Connect(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null)
{
await _ConnectSemaphore.WaitAsync();
try
{
if (IsConnected)
{
Log.Warn("API already connected");
throw new InvalidOperationException();
}
if (tcpRpcClient == null)
{
tcpRpcClient = new TcpRpcClient();
}
try
{
await _ConnectAsync(tcpRpcClient, connectionData).ConfigureAwait(false);
_Bootstrap = tcpRpcClient.GetMain<IBootstrap>();
ServerData = await _GetServerData(_Bootstrap);
Session = await _Authenticate(connectionData).ConfigureAwait(false);
ConnectionData = connectionData;
_TcpRpcClient = tcpRpcClient;
tcpRpcClient.ConnectionStateChanged += OnTcpRpcConnectionChanged;
ConnectionStatusChanged?.Invoke(this, FabAccessAPI.ConnectionStatusChanged.Connected);
Log.Info("API connected");
}
catch (System.Exception ex)
{
Log.Warn(ex, "API connect failed");
throw ex;
}
}
finally
{
_ConnectSemaphore.Release();
}
}
/// <summary>
/// Disconnect from a server
/// </summary>
/// <exception cref="InvalidOperationException"> When API-Service is not connected or trying to connect </exception>
public Task Disconnect()
{
if (IsConnected)
{
_TcpRpcClient.Dispose();
}
_Bootstrap = null;
_TcpRpcClient = null;
Session = null;
ConnectionData = null;
ServerData = null;
ConnectionStatusChanged?.Invoke(this, FabAccessAPI.ConnectionStatusChanged.Disconnected);
Log.Info("API disconnected");
return Task.CompletedTask;
}
/// <summary>
/// Try to connect to a server and get ServerData
/// The connection is not maintained
/// </summary>
/// <exception cref="ConnectionException"> When API-Service can not connect to a server </exception>
public async Task<ServerData> TryToConnect(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null)
{
if (tcpRpcClient == null)
{
tcpRpcClient = new TcpRpcClient();
}
await _ConnectAsync(tcpRpcClient, connectionData).ConfigureAwait(false);
IBootstrap bootstrap = tcpRpcClient.GetMain<IBootstrap>();
ServerData serverData = await _GetServerData(bootstrap).ConfigureAwait(false);
tcpRpcClient.Dispose();
return serverData;
}
/// <summary>
/// Public Wrapper to run HeartbeatAsync
/// </summary>
public void Heartbeat(object state)
{
_ = HeartbeatAsync();
}
#endregion
#region Private Methods
private async Task HeartbeatAsync()
{
if(!IsConnected && CanConnect)
{
try
{
await Connect(ConnectionData).ConfigureAwait(false);
}
catch(AuthenticationException)
{
await Disconnect().ConfigureAwait(false);
}
}
}
/// <summary>
/// Validate Certificate
/// TODO: Do some validation
/// </summary>
private bool _RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// TODO Cert Check
return true;
}
/// <summary>
/// Injects SSL as Midlayer in TCPRPCConnection
/// </summary>
/// <exception cref="ConnectionException"></exception>
private Stream InjectSSL(Stream tcpstream)
{
SslStream sslStream = new SslStream(tcpstream, false, new RemoteCertificateValidationCallback(_RemoteCertificateValidationCallback));
try
{
sslStream.ReadTimeout = 5000;
sslStream.AuthenticateAsClient("bffhd");
sslStream.ReadTimeout = -1;
return sslStream;
}
catch (System.Security.Authentication.AuthenticationException exception)
{
sslStream.Close();
Log.Warn(exception);
throw new ConnectionException("TLS failed", exception);
}
catch(IOException exception)
{
sslStream.Close();
Log.Warn(exception);
throw new ConnectionException("TLS failed", new Exceptions.TimeoutException("TLS timeout", exception));
}
}
/// <summary>
/// Connect async to a server with ConnectionData
/// </summary>
/// <exception cref="ConnectionException">Based on RPC Exception</exception>
private async Task _ConnectAsync(TcpRpcClient tcprpcClient, ConnectionData connectionData)
{
tcprpcClient.InjectMidlayer(InjectSSL);
try
{
Task timeoutTask = Task.Delay(5000);
tcprpcClient.Connect(connectionData.Host.Host, connectionData.Host.Port);
await await Task.WhenAny(tcprpcClient.WhenConnected, timeoutTask);
if (timeoutTask.IsCompleted)
{
Exceptions.TimeoutException timeoutException = new Exceptions.TimeoutException();
Log.Warn(timeoutException);
throw new ConnectionException("Connection timeout", timeoutException);
}
}
catch (RpcException exception) when (string.Equals(exception.Message, "TcpRpcClient is unable to connect", StringComparison.Ordinal))
{
Log.Warn(exception);
throw new ConnectionException("RPC Connecting failed", exception);
}
}
/// <summary>
/// Authenticate connection with ConnectionData
/// </summary>
/// <param name="connectionData"> Data to establish a connection to a server </param>
/// <exception cref="AuthenticationException"></exception>
private async Task<Session> _Authenticate(ConnectionData connectionData)
{
IAuthentication? authentication = await _Bootstrap.CreateSession(SASLMechanism.ToString(connectionData.Mechanism)).ConfigureAwait(false);
try
{
return await _SASLAuthenticate(authentication, SASLMechanism.ToString(connectionData.Mechanism), connectionData.Properties).ConfigureAwait(false);
}
catch (System.Exception exception)
{
Log.Warn(exception, "API authenticating failed");
AuthenticationException authenticationException = new AuthenticationException("Authentication failed", exception);
throw authenticationException;
}
}
/// <summary>
/// Authenticate with SASL
/// </summary>
/// <exception cref="BadMechanismException"></exception>
/// <exception cref="InvalidCredentialsException"></exception>
/// <exception cref="AuthenticationFailedException"></exception>
private async Task<Session> _SASLAuthenticate(IAuthentication authentication, string mech, Dictionary<string, object> properties)
{
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();
}
}
/// <summary>
/// Get ServerData from server with tcprpcconnection
/// </summary>
private async Task<ServerData> _GetServerData(IBootstrap bootstrap)
{
ServerData serverData = new ServerData()
{
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 serverData;
}
#endregion
}
}

45
api/ConnectionData.cs Normal file
View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
namespace FabAccessAPI
{
/// <summary>
/// Data to establish a connection to a server
/// With Data for Authentication
/// </summary>
public class ConnectionData
{
public Uri Host;
public string Username;
public SASLMechanismEnum Mechanism;
public Dictionary<string, object> Properties;
public DateTime LastTime;
public override bool Equals(object? obj)
{
if(obj is ConnectionData && obj != null)
{
ConnectionData? data = obj as ConnectionData;
return data.Host.Host == Host.Host &&
data.Host.Port == Host.Port &&
data.Mechanism == Mechanism &&
data.Username == Username;
}
return false;
}
public override int GetHashCode()
{
int hashCode = -1151110446;
hashCode = hashCode * -1521134295 + EqualityComparer<Uri>.Default.GetHashCode(Host);
hashCode = hashCode * -1521134295 + Mechanism.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Username);
hashCode = hashCode * -1521134295 + EqualityComparer<Dictionary<string, object>>.Default.GetHashCode(Properties);
return hashCode;
}
}
}

View File

@ -0,0 +1,20 @@
namespace FabAccessAPI
{
public enum ConnectionStatusChanged
{
/// <summary>
/// API-Service has established connection to server
/// </summary>
Connected,
/// <summary>
/// API-Service has closed the connection to a server
/// </summary>
Disconnected,
/// <summary>
/// Connection was lost and the API-Service will try to reconnect automatically
/// </summary>
ConnectionLoss
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace FabAccessAPI.Exceptions
{
public class APIIncompatibleException : Exception
{
public APIIncompatibleException()
{
}
public APIIncompatibleException(string message) : base(message)
{
}
public APIIncompatibleException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,26 @@
using System;
namespace FabAccessAPI.Exceptions
{
/// <summary>
/// Authenticating to a server has failed
/// InnerException will provide more information
/// </summary>
public class AuthenticationException : Exception
{
public AuthenticationException()
{
}
public AuthenticationException(string message) : base(message)
{
}
public AuthenticationException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,26 @@
using System;
namespace FabAccessAPI.Exceptions
{
/// <summary>
/// Connecting to a server has failed
/// InnerException will provide more information
/// </summary>
public class ConnectionException : Exception
{
public ConnectionException()
{
}
public ConnectionException(string message) : base(message)
{
}
public ConnectionException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace FabAccessAPI.Exceptions
{
public class ReconnectingFailedException : Exception
{
public ReconnectingFailedException()
{
}
public ReconnectingFailedException(string message) : base(message)
{
}
public ReconnectingFailedException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,28 @@
using System;
namespace FabAccessAPI.Exceptions.SASL
{
public class AuthenticationFailedException : Exception
{
#region Constructors
public AuthenticationFailedException(byte[] additionalData = null)
{
AdditionalData = additionalData;
}
public AuthenticationFailedException(byte[] additionalData, string message) : base(message)
{
AdditionalData = additionalData;
}
public AuthenticationFailedException(byte[] additionalData, string message, Exception inner) : base(message, inner)
{
AdditionalData = additionalData;
}
#endregion
#region Fields
public byte[]? AdditionalData { get; }
#endregion
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace FabAccessAPI.Exceptions.SASL
{
public class BadMechanismException : Exception
{
public BadMechanismException()
{
}
public BadMechanismException(string message) : base(message)
{
}
public BadMechanismException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace FabAccessAPI.Exceptions.SASL
{
public class InvalidCredentialsException : Exception
{
public InvalidCredentialsException()
{
}
public InvalidCredentialsException(string message) : base(message)
{
}
public InvalidCredentialsException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,25 @@
using System;
namespace FabAccessAPI.Exceptions
{
/// <summary>
/// Timeout on Connection
/// </summary>
public class TimeoutException : Exception
{
public TimeoutException()
{
}
public TimeoutException(string message) : base(message)
{
}
public TimeoutException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace FabAccessAPI.Exceptions
{
public class UnsupportedMechanismException : Exception
{
public UnsupportedMechanismException()
{
}
public UnsupportedMechanismException(string message) : base(message)
{
}
public UnsupportedMechanismException(string message, Exception inner) : base(message, inner)
{
}
}
}

24
api/FabAccessAPI.csproj Normal file
View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>8</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;CS8625;CS8602;CS8603;CS8601;CS8619;CS8618;CS8620</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.3.118" />
<PackageReference Include="Microsoft.Extensions.Logging.Log4Net.AspNetCore" Version="6.1.0" />
<PackageReference Include="NLog" Version="5.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\external\capnproto-dotnetcore_Runtime\Capnp.Net.Runtime\Capnp.Net.Runtime.csproj" />
<ProjectReference Include="..\external\S22.Sasl\S22.Sasl.csproj" />
</ItemGroup>
</Project>

84
api/IAPI.cs Normal file
View File

@ -0,0 +1,84 @@
using System;
using System.Threading.Tasks;
using Capnp.Rpc;
using FabAccessAPI.Exceptions;
using FabAccessAPI.Schema;
namespace FabAccessAPI
{
/// <summary>
/// Service to connect to a server and maintain the connection
/// </summary>
public interface IAPI
{
#region Information about a connection and the server
/// <summary>
/// State of the conneciton, is the API-Service connecting to a server
/// </summary>
bool IsConnecting { get; }
/// <summary>
/// State of the conneciton, is the API-Service connected to a server
/// </summary>
bool IsConnected { get; }
/// <summary>
/// Information about the connection
/// </summary>
/// <exception cref="InvalidOperationException"> When API-Service is not connected or trying to connected to a server </exception>
ConnectionData ConnectionData { get; }
/// <summary>
/// Information about the server
/// Is only avalible if the API-Service is connected
/// </summary>
/// <exception cref="InvalidOperationException"> When API-Service is not connected </exception>
ServerData ServerData { get; }
#endregion
#region Methods to connect to server
/// <summary>
/// Connect to server with ConnectionData
/// If connection lost, the API-Server will try to reconnect
/// </summary>
/// <param name="connectionData"> Data to establish a connection to a server </param>
/// <exception cref="ConnectionException"> When API-Service can not connect to a server </exception>
/// <exception cref="AuthenticationException"> When API-Service can connect to a server but can not authenticate </exception>
/// <exception cref="InvalidOperationException"> When API-Service is allready connected </exception>
Task Connect(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null);
/// <summary>
/// Disconnect from a server
/// </summary>
/// <exception cref="InvalidOperationException"> When API-Service is not connected or trying to connect </exception>
Task Disconnect();
/// <summary>
/// Try to connect to a server and get ServerData
/// The connection is not maintained
/// </summary>
/// <exception cref="ConnectionException"> When API-Service can not connect to a server </exception>
Task<ServerData> TryToConnect(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null);
#endregion
#region Session
/// <summary>
/// Get session after connection
/// </summary>
/// <exception cref="InvalidOperationException"> When API-Service is not connected </exception>
Session Session { get; }
#endregion
#region Events
/// <summary>
/// Event on changes in connection status
/// </summary>
event EventHandler<ConnectionStatusChanged> ConnectionStatusChanged;
/// <summary>
/// Unbind all handlers from EventHandler<ConnectionStatusChanged>
/// </summary>
void UnbindEventHandler();
#endregion
}
}

24
api/SASLMechanismEnum.cs Normal file
View File

@ -0,0 +1,24 @@
using System;
namespace FabAccessAPI
{
public enum SASLMechanismEnum
{
PLAIN,
}
public static class SASLMechanism
{
public static string ToString(SASLMechanismEnum mechanism)
{
switch(mechanism)
{
case SASLMechanismEnum.PLAIN:
return "PLAIN";
default:
throw new ArgumentException("Mechanism unknown.");
}
}
}
}

13
api/ServerData.cs Normal file
View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
namespace FabAccessAPI
{
public class ServerData
{
public Schema.Version APIVersion;
public string ServerName;
public string ServerRelease;
public List<string> Mechanisms;
}
}

1
api/schema Submodule

@ -0,0 +1 @@
Subproject commit ec8352c6ae97a0c0b75ad8bcdd5b0cb156f753e7

1
external/S22.Sasl vendored Submodule

@ -0,0 +1 @@
Subproject commit 7316498e9bf9b6380227823f9ae120de09a896ce

1
external/Shell vendored Submodule

@ -0,0 +1 @@
Subproject commit dffd61592ff1473cce9eba86d9a58a8ef3c9d07a

@ -0,0 +1 @@
Subproject commit 1be9ffbf8acbc9770f2d5b67c961fb60e49e5ec5

1
external/nfc vendored Submodule

@ -0,0 +1 @@
Subproject commit a6479be14e86add7a84b3a1a502608a665ec51f0