From 443d4b2ea7cfec3aa44e2a21e0a3a9fa3087d22e Mon Sep 17 00:00:00 2001 From: vladzvx Date: Wed, 3 Apr 2024 00:10:11 +0300 Subject: [PATCH] first commit --- .gitignore | 276 +++++++++++++++++ src/.dockerignore | 30 ++ src/Sphagnum.Client/ClientConnection.cs | 13 + src/Sphagnum.Client/ClientDefault.cs | 79 +++++ src/Sphagnum.Client/Sphagnum.Client.csproj | 16 + .../Comparers/MessagesComparer.cs | 47 +++ .../ConnectionTests.cs | 17 + .../DataGenerators/MessagesGenerator.cs | 23 ++ src/Sphagnum.Common.UnitTests/GlobalUsings.cs | 1 + .../MessageParserTests.cs | 159 ++++++++++ .../Services/TestConnection.cs | 82 +++++ .../Services/TestConnectionFactory.cs | 18 ++ .../Sphagnum.Common.UnitTests.csproj | 24 ++ .../TestConnectionTests.cs | 158 ++++++++++ src/Sphagnum.Common/AssemblySettings.cs | 7 + .../Administration/Enums/ExchangeType.cs | 14 + .../Administration/Enums/TopicType.cs | 8 + .../Administration/IAdministrationClient.cs | 15 + .../Requests/ExchangeCreationRequest.cs | 10 + .../Requests/TopicBindingRequest.cs | 11 + .../Requests/TopicCreationRequest.cs | 10 + src/Sphagnum.Common/Contracts/Constants.cs | 8 + .../Contracts/ISphagnumClient.cs | 8 + .../Contracts/Infrastructure/IConnection.cs | 26 ++ .../Contracts/Login/ConnectionFactory.cs | 28 ++ .../Contracts/Login/UserData.cs | 13 + .../Contracts/Login/UserRights.cs | 19 ++ .../Contracts/Messaging/IMessagingClient.cs | 16 + .../Messaging/Messages/AuthMessage.cs | 22 ++ .../Messaging/Messages/AuthResultMessage.cs | 9 + .../Messaging/Messages/IncommingMessage.cs | 17 + .../Messaging/Messages/OutgoingMessage.cs | 27 ++ .../Contracts/Messaging/RoutingKey.cs | 18 ++ .../Exceptions/AuthException.cs | 8 + src/Sphagnum.Common/Services/ChannelsPool.cs | 36 +++ .../Services/SocketConnection.cs | 62 ++++ .../Services/SphagnumConnection.cs | 143 +++++++++ src/Sphagnum.Common/Sphagnum.Common.csproj | 12 + .../Utils/Enums/MessageFlags.cs | 13 + .../Utils/Enums/MessageType.cs | 18 ++ src/Sphagnum.Common/Utils/HashCalculator.cs | 14 + src/Sphagnum.Common/Utils/MessageParser.cs | 291 ++++++++++++++++++ .../Controllers/TestController.cs | 42 +++ src/Sphagnum.DebugClient/Dockerfile | 27 ++ src/Sphagnum.DebugClient/Program.cs | 26 ++ .../Sphagnum.DebugClient.csproj | 21 ++ .../appsettings.Development.json | 8 + src/Sphagnum.DebugClient/appsettings.json | 9 + src/Sphagnum.DebugService/Dockerfile | 27 ++ src/Sphagnum.DebugService/Program.cs | 12 + .../Sphagnum.DebugService.csproj | 21 ++ .../appsettings.Development.json | 8 + src/Sphagnum.DebugService/appsettings.json | 9 + src/Sphagnum.Server.FuncTests/GlobalUsings.cs | 1 + .../Sphagnum.Server.FuncTests.csproj | 25 ++ src/Sphagnum.Server.FuncTests/UnitTest1.cs | 39 +++ src/Sphagnum.Server/AssemblySettings.cs | 3 + .../Broker/Services/BrokerDefaultBase.cs | 65 ++++ .../Broker/Services/BrokerHost.cs | 26 ++ .../Broker/Services/MessagesProcessor.cs | 68 ++++ .../Cluster/Contracts/IDistributor.cs | 7 + .../Cluster/Services/DistributorDefault.cs | 12 + .../Contracts/IDataProcessor.cs | 9 + .../Services/DataProcessorDefault.cs | 29 ++ src/Sphagnum.Server/Sphagnum.Server.csproj | 17 + .../Messages/Contracts/IMessagesStorage.cs | 7 + .../Services/MessagesStorageDefault.cs | 12 + .../Users/Contracts/IAuthInfoStorage.cs | 13 + .../Users/Services/AuthInfoStorageBase.cs | 30 ++ .../Controllers/TestController.cs | 15 + src/Sphagnum.Service/Program.cs | 14 + src/Sphagnum.Service/Sphagnum.Service.csproj | 18 ++ .../appsettings.Development.json | 8 + src/Sphagnum.Service/appsettings.json | 9 + src/Sphagnum.sln | 89 ++++++ src/docker-compose.dcproj | 19 ++ src/docker-compose.override.yml | 2 + src/docker-compose.yml | 22 ++ src/launchSettings.json | 12 + 79 files changed, 2607 insertions(+) create mode 100644 .gitignore create mode 100644 src/.dockerignore create mode 100644 src/Sphagnum.Client/ClientConnection.cs create mode 100644 src/Sphagnum.Client/ClientDefault.cs create mode 100644 src/Sphagnum.Client/Sphagnum.Client.csproj create mode 100644 src/Sphagnum.Common.UnitTests/Comparers/MessagesComparer.cs create mode 100644 src/Sphagnum.Common.UnitTests/ConnectionTests.cs create mode 100644 src/Sphagnum.Common.UnitTests/DataGenerators/MessagesGenerator.cs create mode 100644 src/Sphagnum.Common.UnitTests/GlobalUsings.cs create mode 100644 src/Sphagnum.Common.UnitTests/MessageParserTests.cs create mode 100644 src/Sphagnum.Common.UnitTests/Services/TestConnection.cs create mode 100644 src/Sphagnum.Common.UnitTests/Services/TestConnectionFactory.cs create mode 100644 src/Sphagnum.Common.UnitTests/Sphagnum.Common.UnitTests.csproj create mode 100644 src/Sphagnum.Common.UnitTests/TestConnectionTests.cs create mode 100644 src/Sphagnum.Common/AssemblySettings.cs create mode 100644 src/Sphagnum.Common/Contracts/Administration/Enums/ExchangeType.cs create mode 100644 src/Sphagnum.Common/Contracts/Administration/Enums/TopicType.cs create mode 100644 src/Sphagnum.Common/Contracts/Administration/IAdministrationClient.cs create mode 100644 src/Sphagnum.Common/Contracts/Administration/Requests/ExchangeCreationRequest.cs create mode 100644 src/Sphagnum.Common/Contracts/Administration/Requests/TopicBindingRequest.cs create mode 100644 src/Sphagnum.Common/Contracts/Administration/Requests/TopicCreationRequest.cs create mode 100644 src/Sphagnum.Common/Contracts/Constants.cs create mode 100644 src/Sphagnum.Common/Contracts/ISphagnumClient.cs create mode 100644 src/Sphagnum.Common/Contracts/Infrastructure/IConnection.cs create mode 100644 src/Sphagnum.Common/Contracts/Login/ConnectionFactory.cs create mode 100644 src/Sphagnum.Common/Contracts/Login/UserData.cs create mode 100644 src/Sphagnum.Common/Contracts/Login/UserRights.cs create mode 100644 src/Sphagnum.Common/Contracts/Messaging/IMessagingClient.cs create mode 100644 src/Sphagnum.Common/Contracts/Messaging/Messages/AuthMessage.cs create mode 100644 src/Sphagnum.Common/Contracts/Messaging/Messages/AuthResultMessage.cs create mode 100644 src/Sphagnum.Common/Contracts/Messaging/Messages/IncommingMessage.cs create mode 100644 src/Sphagnum.Common/Contracts/Messaging/Messages/OutgoingMessage.cs create mode 100644 src/Sphagnum.Common/Contracts/Messaging/RoutingKey.cs create mode 100644 src/Sphagnum.Common/Exceptions/AuthException.cs create mode 100644 src/Sphagnum.Common/Services/ChannelsPool.cs create mode 100644 src/Sphagnum.Common/Services/SocketConnection.cs create mode 100644 src/Sphagnum.Common/Services/SphagnumConnection.cs create mode 100644 src/Sphagnum.Common/Sphagnum.Common.csproj create mode 100644 src/Sphagnum.Common/Utils/Enums/MessageFlags.cs create mode 100644 src/Sphagnum.Common/Utils/Enums/MessageType.cs create mode 100644 src/Sphagnum.Common/Utils/HashCalculator.cs create mode 100644 src/Sphagnum.Common/Utils/MessageParser.cs create mode 100644 src/Sphagnum.DebugClient/Controllers/TestController.cs create mode 100644 src/Sphagnum.DebugClient/Dockerfile create mode 100644 src/Sphagnum.DebugClient/Program.cs create mode 100644 src/Sphagnum.DebugClient/Sphagnum.DebugClient.csproj create mode 100644 src/Sphagnum.DebugClient/appsettings.Development.json create mode 100644 src/Sphagnum.DebugClient/appsettings.json create mode 100644 src/Sphagnum.DebugService/Dockerfile create mode 100644 src/Sphagnum.DebugService/Program.cs create mode 100644 src/Sphagnum.DebugService/Sphagnum.DebugService.csproj create mode 100644 src/Sphagnum.DebugService/appsettings.Development.json create mode 100644 src/Sphagnum.DebugService/appsettings.json create mode 100644 src/Sphagnum.Server.FuncTests/GlobalUsings.cs create mode 100644 src/Sphagnum.Server.FuncTests/Sphagnum.Server.FuncTests.csproj create mode 100644 src/Sphagnum.Server.FuncTests/UnitTest1.cs create mode 100644 src/Sphagnum.Server/AssemblySettings.cs create mode 100644 src/Sphagnum.Server/Broker/Services/BrokerDefaultBase.cs create mode 100644 src/Sphagnum.Server/Broker/Services/BrokerHost.cs create mode 100644 src/Sphagnum.Server/Broker/Services/MessagesProcessor.cs create mode 100644 src/Sphagnum.Server/Cluster/Contracts/IDistributor.cs create mode 100644 src/Sphagnum.Server/Cluster/Services/DistributorDefault.cs create mode 100644 src/Sphagnum.Server/DataProcessing/Contracts/IDataProcessor.cs create mode 100644 src/Sphagnum.Server/DataProcessing/Services/DataProcessorDefault.cs create mode 100644 src/Sphagnum.Server/Sphagnum.Server.csproj create mode 100644 src/Sphagnum.Server/Storage/Messages/Contracts/IMessagesStorage.cs create mode 100644 src/Sphagnum.Server/Storage/Messages/Services/MessagesStorageDefault.cs create mode 100644 src/Sphagnum.Server/Storage/Users/Contracts/IAuthInfoStorage.cs create mode 100644 src/Sphagnum.Server/Storage/Users/Services/AuthInfoStorageBase.cs create mode 100644 src/Sphagnum.Service/Controllers/TestController.cs create mode 100644 src/Sphagnum.Service/Program.cs create mode 100644 src/Sphagnum.Service/Sphagnum.Service.csproj create mode 100644 src/Sphagnum.Service/appsettings.Development.json create mode 100644 src/Sphagnum.Service/appsettings.json create mode 100644 src/Sphagnum.sln create mode 100644 src/docker-compose.dcproj create mode 100644 src/docker-compose.override.yml create mode 100644 src/docker-compose.yml create mode 100644 src/launchSettings.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..69eb2c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,276 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +env/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +output/ +dist/ +[Pp]roperties/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj +*.pwd +*.env + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +build/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + + +#deploy +deploy*.bat +.github + +docker-compose-env/ +*.env diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 0000000..4d72b4f --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/src/Sphagnum.Client/ClientConnection.cs b/src/Sphagnum.Client/ClientConnection.cs new file mode 100644 index 0000000..1103772 --- /dev/null +++ b/src/Sphagnum.Client/ClientConnection.cs @@ -0,0 +1,13 @@ +namespace Sphagnum.Client +{ + //internal class ClientConnection : SocketConnection + //{ + // public ClientConnection() : base( async (dd) => + // { + + // }) + // { + + // } + //} +} diff --git a/src/Sphagnum.Client/ClientDefault.cs b/src/Sphagnum.Client/ClientDefault.cs new file mode 100644 index 0000000..487e7ff --- /dev/null +++ b/src/Sphagnum.Client/ClientDefault.cs @@ -0,0 +1,79 @@ +using Sphagnum.Common.Contracts.Login; +using Sphagnum.Common.Contracts.Messaging; +using Sphagnum.Common.Contracts.Messaging.Messages; +using Sphagnum.Common.Services; +using Sphagnum.Common.Utils; +using System; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; + +namespace Sphagnum.Client +{ + public class ClientDefault : IMessagingClient, IDisposable + { + private readonly SphagnumConnection _connection; + private readonly ConnectionFactory _connectionFactory; + private readonly Channel _commonMessagesChannel = Channel.CreateUnbounded(); + private readonly CancellationTokenSource _cts = new CancellationTokenSource(); + public ClientDefault(ConnectionFactory factory) + { + _connectionFactory = factory; + _connection = factory.CreateDefaultConnected(() => async (mess) => + { + await _commonMessagesChannel.Writer.WriteAsync(mess); + }).Result; + Auth().Wait(); + } + + private async Task ReceiveAsync() + { + return await _commonMessagesChannel.Reader.ReadAsync(_cts.Token); + } + + private async Task Auth() + { + await _connection.SendAsync(MessageParser.PackMessage(new AuthMessage(_connectionFactory.Login, _connectionFactory.Password, _connectionFactory.UserRights))); + var response = await ReceiveAsync(); + var messageType = MessageParser.GetMessageType(response); + if (messageType == Common.Utils.Models.MessageType.AuthSuccessfull) + { + return; + } + throw new Exception("Auth failed!"); + } + + public ValueTask Ack(Guid messageId) + { + throw new NotImplementedException(); + } + + public ValueTask Nack(Guid messageId) + { + throw new NotImplementedException(); + } + + public async ValueTask Publish(OutgoingMessage message) + { + var bytes = MessageParser.PackMessage(message); + await _connection.SendAsync(bytes); + return MessageParser.GetMessageId(bytes); + } + + public async ValueTask Consume(CancellationToken cancellationToken) + { + var result = await _commonMessagesChannel.Reader.ReadAsync(cancellationToken); + return MessageParser.UnpackIncomingMessage(result); + } + + public ValueTask Reject(Guid messageId) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + _cts.Cancel(); + } + } +} diff --git a/src/Sphagnum.Client/Sphagnum.Client.csproj b/src/Sphagnum.Client/Sphagnum.Client.csproj new file mode 100644 index 0000000..6a43857 --- /dev/null +++ b/src/Sphagnum.Client/Sphagnum.Client.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.1 + enable + + + + + + + + + + + diff --git a/src/Sphagnum.Common.UnitTests/Comparers/MessagesComparer.cs b/src/Sphagnum.Common.UnitTests/Comparers/MessagesComparer.cs new file mode 100644 index 0000000..45af8ad --- /dev/null +++ b/src/Sphagnum.Common.UnitTests/Comparers/MessagesComparer.cs @@ -0,0 +1,47 @@ +using Sphagnum.Common.Contracts.Messaging.Messages; + +namespace Sphagnum.Common.UnitTests.Comparers +{ + internal static class MessagesComparer + { + public static bool Compare(OutgoingMessage message1, OutgoingMessage message2) + { + var res = true; + res &= message1.Exchange == message2.Exchange; + res &= message1.RoutingKey.Part1 == message2.RoutingKey.Part1; + res &= message1.RoutingKey.Part2 == message2.RoutingKey.Part2; + res &= message1.RoutingKey.Part3 == message2.RoutingKey.Part3; + res &= message1.Payload.Length == message2.Payload.Length; + res &= ComparePayloads(message1, message2); + return res; + } + + public static bool Compare(IncommingMessage message1, IncommingMessage message2) + { + var res = true; + res &= message1.MessageId == message2.MessageId; + res &= ComparePayloads(message1.Payload.ToArray(), message2.Payload.ToArray()); + return res; + } + + public static bool ComparePayloads(OutgoingMessage message1, OutgoingMessage message2) + { + var payload1 = message1.Payload.ToArray(); + var payload2 = message2.Payload.ToArray(); + return ComparePayloads(payload1, payload2); + } + + public static bool ComparePayloads(byte[] payload1, byte[] payload2) + { + var res = true; + if (res) + { + for (int i = 0; i < payload1.Length; i++) + { + res &= payload1[i] == payload2[i]; + } + } + return res; + } + } +} diff --git a/src/Sphagnum.Common.UnitTests/ConnectionTests.cs b/src/Sphagnum.Common.UnitTests/ConnectionTests.cs new file mode 100644 index 0000000..797f546 --- /dev/null +++ b/src/Sphagnum.Common.UnitTests/ConnectionTests.cs @@ -0,0 +1,17 @@ +namespace Sphagnum.Common.UnitTests +{ + public class ConnectionTests + { + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + Assert.Pass(); + } + } + +} diff --git a/src/Sphagnum.Common.UnitTests/DataGenerators/MessagesGenerator.cs b/src/Sphagnum.Common.UnitTests/DataGenerators/MessagesGenerator.cs new file mode 100644 index 0000000..c5ba76a --- /dev/null +++ b/src/Sphagnum.Common.UnitTests/DataGenerators/MessagesGenerator.cs @@ -0,0 +1,23 @@ +using Sphagnum.Common.Contracts.Messaging; +using Sphagnum.Common.Contracts.Messaging.Messages; +using System.Security.Cryptography; + +namespace Sphagnum.Common.UnitTests.DataGenerators +{ + internal static class MessagesGenerator + { + public static OutgoingMessage GetRandomOutgoingMessage(bool emptyKey = false, bool emptyPayload = false) + { + var exchangeName = RandomNumberGenerator.GetInt32(10000000).ToString(); + var payload = !emptyPayload ? RandomNumberGenerator.GetBytes(RandomNumberGenerator.GetInt32(0, 1000)) : []; + var routingKeysBytes = RandomNumberGenerator.GetBytes(3); + return new OutgoingMessage(exchangeName, !emptyKey ? new RoutingKey(routingKeysBytes[0], routingKeysBytes[1], routingKeysBytes[2]) : new RoutingKey(0, 0, 0), payload); + } + + public static IncommingMessage GetRandoIncommingMessage(bool emptyPayload = false) + { + var payload = !emptyPayload ? RandomNumberGenerator.GetBytes(RandomNumberGenerator.GetInt32(0, 1000)) : []; + return new IncommingMessage(Guid.NewGuid(), payload); + } + } +} diff --git a/src/Sphagnum.Common.UnitTests/GlobalUsings.cs b/src/Sphagnum.Common.UnitTests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/src/Sphagnum.Common.UnitTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/src/Sphagnum.Common.UnitTests/MessageParserTests.cs b/src/Sphagnum.Common.UnitTests/MessageParserTests.cs new file mode 100644 index 0000000..f09fe20 --- /dev/null +++ b/src/Sphagnum.Common.UnitTests/MessageParserTests.cs @@ -0,0 +1,159 @@ +using Sphagnum.Common.UnitTests.DataGenerators; +using Sphagnum.Common.Utils; +using Sphagnum.Common.Utils.Models; + +namespace Sphagnum.Common.UnitTests +{ + public class MessageParserTests + { + [Test] + public void PackUnpackIncomingMessage_WithPayload() + { + var count = 0; + while (count < 100) + { + var message = MessagesGenerator.GetRandoIncommingMessage(); + var bytes = MessageParser.PackMessage(message); + var message2 = MessageParser.UnpackIncomingMessage(bytes); + Assert.That(Comparers.MessagesComparer.Compare(message, message2), Is.True); + + count++; + } + } + + [Test] + public void PackUnpackIncomingMessage_WithEmptyPayload() + { + var count = 0; + while (count < 100) + { + var message = MessagesGenerator.GetRandoIncommingMessage(true); + var bytes = MessageParser.PackMessage(message); + var message2 = MessageParser.UnpackIncomingMessage(bytes); + Assert.That(Comparers.MessagesComparer.Compare(message, message2), Is.True); + Assert.IsTrue((MessageFlags)BitConverter.ToUInt16(bytes.AsSpan(5, 2)) == MessageFlags.HasPayload); + count++; + } + } + + [Test] + public void PackUnpackOutgoingMessageGetMessageId_WithRoutingKeyAndPayload() + { + var count = 0; + while (count < 100) + { + var id = Guid.NewGuid(); + var message = MessagesGenerator.GetRandomOutgoingMessage(); + var bytesForFlags = MessageParser.PackMessage(message); + var flags = (MessageFlags)BitConverter.ToUInt16(bytesForFlags.AsSpan(5, 2)); + var bytes = MessageParser.Pack(message, id, flags, bytesForFlags.Length); + + var message2 = MessageParser.UnpackOutgoingMessage(bytes); + Assert.That(Comparers.MessagesComparer.Compare(message, message2), Is.True); + var id2 = MessageParser.GetMessageId(bytes); + Assert.That(id, Is.EqualTo(id2)); + count++; + } + } + + [Test] + public void PackUnpackOutgoingMessageGetMessageId_WithEmptyRoutingKeyAndEmptyPayload() + { + var count = 0; + while (count < 100) + { + var id = Guid.NewGuid(); + var message = MessagesGenerator.GetRandomOutgoingMessage(true, true); + var bytesForFlags = MessageParser.PackMessage(message); + var flags = (MessageFlags)BitConverter.ToUInt16(bytesForFlags.AsSpan(5, 2)); + var bytes = MessageParser.Pack(message, id, flags, bytesForFlags.Length); + + var message2 = MessageParser.UnpackOutgoingMessage(bytes); + Assert.That(Comparers.MessagesComparer.Compare(message, message2), Is.True); + var id2 = MessageParser.GetMessageId(bytes); + Assert.That(id, Is.EqualTo(id2)); + count++; + } + } + + [Test] + public void PackUnpackOutgoingMessage_WithRoutingKeyAndPayload() + { + var count = 0; + while (count < 100) + { + var message = MessagesGenerator.GetRandomOutgoingMessage(); + var bytes = MessageParser.PackMessage(message); + var message2 = MessageParser.UnpackOutgoingMessage(bytes); + Assert.That(Comparers.MessagesComparer.Compare(message, message2), Is.True); + var bytes2 = MessageParser.PackMessage(message2); + var message3 = MessageParser.UnpackOutgoingMessage(bytes2); + Assert.That(Comparers.MessagesComparer.ComparePayloads(message2, message3), Is.True); + var bytes3 = MessageParser.PackMessage(message2); + var message4 = MessageParser.UnpackOutgoingMessage(bytes3); + Assert.That(Comparers.MessagesComparer.ComparePayloads(message3, message4), Is.True); + count++; + } + } + + [Test] + public void PackUnpackOutgoingMessage_WithRoutingKeyAndEmptyPayload() + { + var count = 0; + while (count < 100) + { + var message = MessagesGenerator.GetRandomOutgoingMessage(false, true); + var bytes = MessageParser.PackMessage(message); + var message2 = MessageParser.UnpackOutgoingMessage(bytes); + Assert.That(Comparers.MessagesComparer.Compare(message, message2), Is.True); + var bytes2 = MessageParser.PackMessage(message2); + var message3 = MessageParser.UnpackOutgoingMessage(bytes2); + Assert.That(Comparers.MessagesComparer.ComparePayloads(message2, message3), Is.True); + var bytes3 = MessageParser.PackMessage(message2); + var message4 = MessageParser.UnpackOutgoingMessage(bytes3); + Assert.That(Comparers.MessagesComparer.ComparePayloads(message3, message4), Is.True); + count++; + } + } + + [Test] + public void PackUnpackOutgoingMessage_WithEmptyRoutingKey() + { + var count = 0; + while (count < 100) + { + var message = MessagesGenerator.GetRandomOutgoingMessage(true); + var bytes = MessageParser.PackMessage(message); + var message2 = MessageParser.UnpackOutgoingMessage(bytes); + Assert.That(Comparers.MessagesComparer.Compare(message, message2), Is.True); + var bytes2 = MessageParser.PackMessage(message2); + var message3 = MessageParser.UnpackOutgoingMessage(bytes2); + Assert.That(Comparers.MessagesComparer.ComparePayloads(message2, message3), Is.True); + var bytes3 = MessageParser.PackMessage(message2); + var message4 = MessageParser.UnpackOutgoingMessage(bytes3); + Assert.That(Comparers.MessagesComparer.ComparePayloads(message3, message4), Is.True); + count++; + } + } + + [Test] + public void PackUnpackOutgoingMessage_WithEmptyRoutingKeyAndEmptyPayload() + { + var count = 0; + while (count < 100) + { + var message = MessagesGenerator.GetRandomOutgoingMessage(true, true); + var bytes = MessageParser.PackMessage(message); + var message2 = MessageParser.UnpackOutgoingMessage(bytes); + Assert.That(Comparers.MessagesComparer.Compare(message, message2), Is.True); + var bytes2 = MessageParser.PackMessage(message2); + var message3 = MessageParser.UnpackOutgoingMessage(bytes2); + Assert.That(Comparers.MessagesComparer.ComparePayloads(message2, message3), Is.True); + var bytes3 = MessageParser.PackMessage(message2); + var message4 = MessageParser.UnpackOutgoingMessage(bytes3); + Assert.That(Comparers.MessagesComparer.ComparePayloads(message3, message4), Is.True); + count++; + } + } + } +} \ No newline at end of file diff --git a/src/Sphagnum.Common.UnitTests/Services/TestConnection.cs b/src/Sphagnum.Common.UnitTests/Services/TestConnection.cs new file mode 100644 index 0000000..4c09f2d --- /dev/null +++ b/src/Sphagnum.Common.UnitTests/Services/TestConnection.cs @@ -0,0 +1,82 @@ +using Sphagnum.Common.Contracts.Infrastructure; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; + +namespace Sphagnum.Common.UnitTests.Services +{ + internal class TestConnection : IConnection + { + private readonly ConcurrentQueue _queue = new(); + public bool Connected => true; + + public IConnection Accept() + { + return new TestConnection(); + } + + public Task AcceptAsync() + { + return Task.FromResult(new TestConnection()); + } + + public void Bind(EndPoint endPoint) + { + } + + public void Close() + { + + } + + public Task ConnectAsync(string host, int port) + { + return Task.CompletedTask; + } + + public void Dispose() + { + + } + + public void Listen(int backlog) + { + + } + + public async ValueTask ReceiveAsync(Memory buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default) + { + var res = new byte[buffer.Length]; + await Receive(res, socketFlags, cancellationToken); + res.CopyTo(buffer); + return res.Length; + } + + public ValueTask SendAsync(ReadOnlyMemory buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default) + { + _queue.Enqueue(buffer.Span.ToArray()); + return ValueTask.FromResult(buffer.Length); + } + + private async ValueTask Receive(byte[] buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default, int counter = 0) + { + if (counter > 200) + { + throw new TimeoutException(); + } + + if (socketFlags == SocketFlags.Peek ? _queue.TryPeek(out byte[]? result) : _queue.TryDequeue(out result)) + { + result.CopyTo(buffer, 0); + return result.Length; + } + else + { + await Task.Delay(100, cancellationToken); + counter++; + await Receive(buffer, socketFlags, cancellationToken, counter); + } + throw new TimeoutException(); + } + } +} diff --git a/src/Sphagnum.Common.UnitTests/Services/TestConnectionFactory.cs b/src/Sphagnum.Common.UnitTests/Services/TestConnectionFactory.cs new file mode 100644 index 0000000..a314ab1 --- /dev/null +++ b/src/Sphagnum.Common.UnitTests/Services/TestConnectionFactory.cs @@ -0,0 +1,18 @@ +using Sphagnum.Common.Contracts.Login; +using Sphagnum.Common.Services; + +namespace Sphagnum.Common.UnitTests.Services +{ + internal class TestConnectionFactory : ConnectionFactory + { + internal override SphagnumConnection CreateDefault(Func> messagesProcessorFactory) + { + return new SphagnumConnection(() => new TestConnection(), messagesProcessorFactory); + } + + internal override Task CreateDefaultConnected(Func> messagesProcessorFactory) + { + return Task.FromResult(new SphagnumConnection(() => new TestConnection(), messagesProcessorFactory)); + } + } +} diff --git a/src/Sphagnum.Common.UnitTests/Sphagnum.Common.UnitTests.csproj b/src/Sphagnum.Common.UnitTests/Sphagnum.Common.UnitTests.csproj new file mode 100644 index 0000000..efca1b2 --- /dev/null +++ b/src/Sphagnum.Common.UnitTests/Sphagnum.Common.UnitTests.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/src/Sphagnum.Common.UnitTests/TestConnectionTests.cs b/src/Sphagnum.Common.UnitTests/TestConnectionTests.cs new file mode 100644 index 0000000..e3f7f6c --- /dev/null +++ b/src/Sphagnum.Common.UnitTests/TestConnectionTests.cs @@ -0,0 +1,158 @@ +//using Sphagnum.Common.UnitTests.Services; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; + +//namespace Sphagnum.Common.UnitTests +//{ +// public class TestConnectionTests +// { +// [Test] +// public async Task SendRecieve8BytesWithBufferSize10() +// { +// var connection = new TestConnection +// { +// BufferSize = 10 +// }; +// var data = new byte[8]; +// Array.Fill(data, 1); +// _ = await connection.SendAsync(data); +// var forResult = new List(); +// for (int i = 0; i < 1; i++) +// { +// var buffer = new byte[connection.BufferSize]; +// _ = await connection.ReceiveAsync(buffer); +// for (int j = 0; j < buffer.Length; j++) +// { +// var el = buffer[j]; +// if (el == 1) +// { +// forResult.Add(el); +// } +// } +// Array.Clear(buffer); +// } + +// Assert.IsTrue(data.Length == forResult.Count); +// } + +// [Test] +// public async Task SendRecieve10BytesWithBufferSize10() +// { +// var connection = new TestConnection +// { +// BufferSize = 10 +// }; +// var data = new byte[10]; +// Array.Fill(data, 1); +// _ = await connection.SendAsync(data); +// var forResult = new List(); +// for (int i = 0; i < 1; i++) +// { +// var buffer = new byte[connection.BufferSize]; +// _ = await connection.ReceiveAsync(buffer); +// for (int j = 0; j < buffer.Length; j++) +// { +// var el = buffer[j]; +// if (el == 1) +// { +// forResult.Add(el); +// } +// } +// Array.Clear(buffer); +// } + +// Assert.IsTrue(data.Length == forResult.Count); +// } + +// [Test] +// public async Task SendRecieve11BytesWithBufferSize10() +// { +// var connection = new TestConnection +// { +// BufferSize = 10 +// }; +// var data = new byte[11]; +// Array.Fill(data, 1); +// _ = await connection.SendAsync(data); +// var forResult = new List(); +// for (int i = 0; i < 2; i++) +// { +// var buffer = new byte[connection.BufferSize]; +// _ = await connection.ReceiveAsync(buffer); +// for (int j = 0; j < buffer.Length; j++) +// { +// var el = buffer[j]; +// if (el==1) +// { +// forResult.Add(el); +// } +// } +// Array.Clear(buffer); +// } + +// Assert.IsTrue(data.Length == forResult.Count); +// } + +// [Test] +// public async Task SendRecieve31BytesWithBufferSize10() +// { +// var connection = new TestConnection +// { +// BufferSize = 10 +// }; +// var data = new byte[31]; +// Array.Fill(data, 1); +// _ = await connection.SendAsync(data); +// var forResult = new List(); +// for (int i = 0; i < 4; i++) +// { +// var buffer = new byte[connection.BufferSize]; +// _ = await connection.ReceiveAsync(buffer); +// for (int j = 0; j < buffer.Length; j++) +// { +// var el = buffer[j]; +// if (el == 1) +// { +// forResult.Add(el); +// } +// } +// Array.Clear(buffer); +// } + +// Assert.IsTrue(data.Length == forResult.Count); +// } + +// [Test] +// public async Task SendRecieve30BytesWithBufferSize10() +// { +// var connection = new TestConnection +// { +// BufferSize = 10 +// }; +// var data = new byte[30]; +// Array.Fill(data, 1); +// _ = await connection.SendAsync(data); +// var forResult = new List(); +// for (int i = 0; i < 3; i++) +// { +// var buffer = new byte[connection.BufferSize]; +// _ = await connection.ReceiveAsync(buffer); +// for (int j = 0; j < buffer.Length; j++) +// { +// var el = buffer[j]; +// if (el == 1) +// { +// forResult.Add(el); +// } +// } +// Array.Clear(buffer); +// } + +// Assert.IsTrue(data.Length == forResult.Count); +// } +// } + +//} diff --git a/src/Sphagnum.Common/AssemblySettings.cs b/src/Sphagnum.Common/AssemblySettings.cs new file mode 100644 index 0000000..9eff541 --- /dev/null +++ b/src/Sphagnum.Common/AssemblySettings.cs @@ -0,0 +1,7 @@ +using System.Runtime.CompilerServices; + +//Тесты +[assembly: InternalsVisibleTo("Sphagnum.Common.UnitTests")] + +[assembly: InternalsVisibleTo("Sphagnum.Client")] +[assembly: InternalsVisibleTo("Sphagnum.Server")] \ No newline at end of file diff --git a/src/Sphagnum.Common/Contracts/Administration/Enums/ExchangeType.cs b/src/Sphagnum.Common/Contracts/Administration/Enums/ExchangeType.cs new file mode 100644 index 0000000..e73703f --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Administration/Enums/ExchangeType.cs @@ -0,0 +1,14 @@ +namespace Sphagnum.Common.Contracts.Administration.Enums +{ + public enum ExchangeType : byte + { + /// + /// Раздает сообщения во все топики с подходящим ключём маршрутизации. + /// + Broadcast, + /// + /// Отправляет сообщение в одну из очередей с подходящим ключём маршрутизации. + /// + Topic, + } +} diff --git a/src/Sphagnum.Common/Contracts/Administration/Enums/TopicType.cs b/src/Sphagnum.Common/Contracts/Administration/Enums/TopicType.cs new file mode 100644 index 0000000..febbc20 --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Administration/Enums/TopicType.cs @@ -0,0 +1,8 @@ +namespace Sphagnum.Common.Contracts.Administration.Enums +{ + public enum TopicType + { + Queue, + Stack, + } +} diff --git a/src/Sphagnum.Common/Contracts/Administration/IAdministrationClient.cs b/src/Sphagnum.Common/Contracts/Administration/IAdministrationClient.cs new file mode 100644 index 0000000..391a18c --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Administration/IAdministrationClient.cs @@ -0,0 +1,15 @@ +using Sphagnum.Common.Contracts.Administration.Requests; +using System.Threading.Tasks; + +namespace Sphagnum.Common.Contracts.Administration +{ + public interface IAdministrationClient + { + public ValueTask CreateExchange(ExchangeCreationRequest exchangeCreationRequest); + public ValueTask CreateTopic(TopicCreationRequest topicCreationRequest); + public ValueTask DeleteTopic(string topicName); + public ValueTask DeleteExchange(string exchangeName); + public ValueTask BindTopic(TopicBindingRequest topicCreationRequest); + public ValueTask UnbindTopic(TopicBindingRequest topicCreationRequest); + } +} diff --git a/src/Sphagnum.Common/Contracts/Administration/Requests/ExchangeCreationRequest.cs b/src/Sphagnum.Common/Contracts/Administration/Requests/ExchangeCreationRequest.cs new file mode 100644 index 0000000..3084b78 --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Administration/Requests/ExchangeCreationRequest.cs @@ -0,0 +1,10 @@ +using Sphagnum.Common.Contracts.Administration.Enums; + +namespace Sphagnum.Common.Contracts.Administration.Requests +{ + public readonly ref struct ExchangeCreationRequest + { + public readonly string ExchangeName; + public readonly ExchangeType ExchangeType; + } +} diff --git a/src/Sphagnum.Common/Contracts/Administration/Requests/TopicBindingRequest.cs b/src/Sphagnum.Common/Contracts/Administration/Requests/TopicBindingRequest.cs new file mode 100644 index 0000000..1a77e7c --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Administration/Requests/TopicBindingRequest.cs @@ -0,0 +1,11 @@ +using Sphagnum.Common.Contracts.Messaging; + +namespace Sphagnum.Common.Contracts.Administration.Requests +{ + public readonly ref struct TopicBindingRequest + { + public readonly string TopicName; + public readonly string ExchangeName; + public readonly RoutingKey RoutingKey; + } +} diff --git a/src/Sphagnum.Common/Contracts/Administration/Requests/TopicCreationRequest.cs b/src/Sphagnum.Common/Contracts/Administration/Requests/TopicCreationRequest.cs new file mode 100644 index 0000000..c603b50 --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Administration/Requests/TopicCreationRequest.cs @@ -0,0 +1,10 @@ +using Sphagnum.Common.Contracts.Administration.Enums; + +namespace Sphagnum.Common.Contracts.Administration.Requests +{ + public readonly ref struct TopicCreationRequest + { + public readonly string TopicName; + public readonly TopicType TopicType; + } +} diff --git a/src/Sphagnum.Common/Contracts/Constants.cs b/src/Sphagnum.Common/Contracts/Constants.cs new file mode 100644 index 0000000..280bb9a --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Constants.cs @@ -0,0 +1,8 @@ +namespace Sphagnum.Common +{ + internal static class Constants + { + public const int HashedUserDataSizeInfBytes = 32; + public const int PayloadRecieverBufferSize = 8192; + } +} diff --git a/src/Sphagnum.Common/Contracts/ISphagnumClient.cs b/src/Sphagnum.Common/Contracts/ISphagnumClient.cs new file mode 100644 index 0000000..26bd939 --- /dev/null +++ b/src/Sphagnum.Common/Contracts/ISphagnumClient.cs @@ -0,0 +1,8 @@ +using Sphagnum.Common.Contracts.Messaging; + +namespace Sphagnum.Common.Contracts +{ + public interface ISphagnumClient : IMessagingClient + { + } +} diff --git a/src/Sphagnum.Common/Contracts/Infrastructure/IConnection.cs b/src/Sphagnum.Common/Contracts/Infrastructure/IConnection.cs new file mode 100644 index 0000000..5f0713c --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Infrastructure/IConnection.cs @@ -0,0 +1,26 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Sphagnum.Common.Contracts.Infrastructure +{ + internal interface IConnection : IDisposable + { + Task ConnectAsync(string host, int port); + Task AcceptAsync(); + //todo прописать бросаемые исключения + void Bind(EndPoint endPoint); + + ValueTask ReceiveAsync(Memory buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default); + + //todo прописать бросаемые исключения + void Listen(int backlog); + //todo прописать бросаемые исключения + ValueTask SendAsync(ReadOnlyMemory buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default); + //todo прописать бросаемые исключения + void Close(); + bool Connected { get; } + } +} diff --git a/src/Sphagnum.Common/Contracts/Login/ConnectionFactory.cs b/src/Sphagnum.Common/Contracts/Login/ConnectionFactory.cs new file mode 100644 index 0000000..9309e95 --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Login/ConnectionFactory.cs @@ -0,0 +1,28 @@ +using Sphagnum.Common.Services; +using System; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace Sphagnum.Common.Contracts.Login +{ + public class ConnectionFactory + { + public int Port { get; set; } + public string Hostname { get; set; } = string.Empty; + public string Login { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + public UserRights UserRights { get; set; } + + internal virtual async Task CreateDefaultConnected(Func> messagesProcessorFactory) + { + var conn = new SphagnumConnection(() => new SocketConnection(new Socket(SocketType.Stream, ProtocolType.Tcp)), messagesProcessorFactory); + await conn.ConnectAsync(Hostname, Port); + return conn; + } + + internal virtual SphagnumConnection CreateDefault(Func> messagesProcessorFactory) + { + return new SphagnumConnection(() => new SocketConnection(new Socket(SocketType.Stream, ProtocolType.Tcp)), messagesProcessorFactory); + } + } +} diff --git a/src/Sphagnum.Common/Contracts/Login/UserData.cs b/src/Sphagnum.Common/Contracts/Login/UserData.cs new file mode 100644 index 0000000..6284463 --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Login/UserData.cs @@ -0,0 +1,13 @@ +namespace Sphagnum.Common.Contracts.Login +{ + public class UserData + { + public string Username { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + public int Port { get; set; } + public string Hostname { get; set; } = string.Empty; + public UserRights UserRights { get; set; } = + UserRights.MessagesConsuming | + UserRights.MessagesPublishing; + } +} diff --git a/src/Sphagnum.Common/Contracts/Login/UserRights.cs b/src/Sphagnum.Common/Contracts/Login/UserRights.cs new file mode 100644 index 0000000..41d02eb --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Login/UserRights.cs @@ -0,0 +1,19 @@ +using System; + +namespace Sphagnum.Common.Contracts.Login +{ + [Flags] + public enum UserRights : ushort + { + None = 0, + MessagesConsuming = 1, + MessagesPublishing = 2, + TopicCreating = 4, + TopicDeleting = 8, + TopicBinding = 16, + ExchangeCreating = 32, + ExchangeDeleting = 64, + + All = MessagesConsuming | MessagesPublishing | TopicCreating | TopicDeleting | TopicBinding | ExchangeCreating | ExchangeDeleting, + } +} diff --git a/src/Sphagnum.Common/Contracts/Messaging/IMessagingClient.cs b/src/Sphagnum.Common/Contracts/Messaging/IMessagingClient.cs new file mode 100644 index 0000000..e9dbe0e --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Messaging/IMessagingClient.cs @@ -0,0 +1,16 @@ +using Sphagnum.Common.Contracts.Messaging.Messages; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Sphagnum.Common.Contracts.Messaging +{ + public interface IMessagingClient + { + public ValueTask Consume(CancellationToken cancellationToken); + public ValueTask Publish(OutgoingMessage message); + public ValueTask Ack(Guid messageId); + public ValueTask Nack(Guid messageId); + public ValueTask Reject(Guid messageId); + } +} diff --git a/src/Sphagnum.Common/Contracts/Messaging/Messages/AuthMessage.cs b/src/Sphagnum.Common/Contracts/Messaging/Messages/AuthMessage.cs new file mode 100644 index 0000000..5efe53b --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Messaging/Messages/AuthMessage.cs @@ -0,0 +1,22 @@ +using Sphagnum.Common.Contracts.Login; +using Sphagnum.Common.Utils; +using System; + +namespace Sphagnum.Common.Contracts.Messaging.Messages +{ + internal readonly ref struct AuthMessage + { + public readonly ReadOnlyMemory Payload; + public readonly Guid MessageId; + + public AuthMessage(string login, string pwd, UserRights userRights) + { + MessageId = Guid.NewGuid(); + var data = new byte[Constants.HashedUserDataSizeInfBytes + Constants.HashedUserDataSizeInfBytes + 2]; + HashCalculator.Calc(login).CopyTo(data, 0); + HashCalculator.Calc(pwd).CopyTo(data, Constants.HashedUserDataSizeInfBytes); + BitConverter.TryWriteBytes(data.AsSpan(Constants.HashedUserDataSizeInfBytes + Constants.HashedUserDataSizeInfBytes), (ushort)userRights); + Payload = data; + } + } +} diff --git a/src/Sphagnum.Common/Contracts/Messaging/Messages/AuthResultMessage.cs b/src/Sphagnum.Common/Contracts/Messaging/Messages/AuthResultMessage.cs new file mode 100644 index 0000000..c19943b --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Messaging/Messages/AuthResultMessage.cs @@ -0,0 +1,9 @@ +using System; + +namespace Sphagnum.Common.Contracts.Messaging.Messages +{ + internal readonly ref struct AuthResultMessage + { + public readonly ReadOnlyMemory Payload; + } +} diff --git a/src/Sphagnum.Common/Contracts/Messaging/Messages/IncommingMessage.cs b/src/Sphagnum.Common/Contracts/Messaging/Messages/IncommingMessage.cs new file mode 100644 index 0000000..b7eade9 --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Messaging/Messages/IncommingMessage.cs @@ -0,0 +1,17 @@ +using System; + +namespace Sphagnum.Common.Contracts.Messaging.Messages +{ + public readonly struct IncommingMessage + { + public readonly Guid MessageId; + + public readonly ReadOnlyMemory Payload; + + public IncommingMessage(Guid messageId, ReadOnlyMemory payload) + { + MessageId = messageId; + Payload = payload; + } + } +} diff --git a/src/Sphagnum.Common/Contracts/Messaging/Messages/OutgoingMessage.cs b/src/Sphagnum.Common/Contracts/Messaging/Messages/OutgoingMessage.cs new file mode 100644 index 0000000..741ec33 --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Messaging/Messages/OutgoingMessage.cs @@ -0,0 +1,27 @@ +using System; + +namespace Sphagnum.Common.Contracts.Messaging.Messages +{ + public readonly struct OutgoingMessage + { + public readonly string Exchange; + + public readonly RoutingKey RoutingKey; + + public readonly ReadOnlyMemory Payload; + + public OutgoingMessage(string exchange, RoutingKey routingKey, ReadOnlyMemory payload) + { + Exchange = exchange; + RoutingKey = routingKey; + Payload = payload; + } + + public OutgoingMessage(string exchange, ReadOnlyMemory payload) + { + Exchange = exchange; + RoutingKey = new RoutingKey(); + Payload = payload; + } + } +} diff --git a/src/Sphagnum.Common/Contracts/Messaging/RoutingKey.cs b/src/Sphagnum.Common/Contracts/Messaging/RoutingKey.cs new file mode 100644 index 0000000..621e2b1 --- /dev/null +++ b/src/Sphagnum.Common/Contracts/Messaging/RoutingKey.cs @@ -0,0 +1,18 @@ +namespace Sphagnum.Common.Contracts.Messaging +{ + public readonly struct RoutingKey + { + public readonly byte Part1; + public readonly byte Part2; + public readonly byte Part3; + + public bool IsEmpry => Part1 == 0 && Part2 == 0 && Part3 == 0; + + public RoutingKey(byte part1, byte part2, byte part3) + { + Part1 = part1; + Part2 = part2; + Part3 = part3; + } + } +} diff --git a/src/Sphagnum.Common/Exceptions/AuthException.cs b/src/Sphagnum.Common/Exceptions/AuthException.cs new file mode 100644 index 0000000..9ba3d60 --- /dev/null +++ b/src/Sphagnum.Common/Exceptions/AuthException.cs @@ -0,0 +1,8 @@ +using System; + +namespace Sphagnum.Common.Exceptions +{ + public class AuthException : Exception + { + } +} diff --git a/src/Sphagnum.Common/Services/ChannelsPool.cs b/src/Sphagnum.Common/Services/ChannelsPool.cs new file mode 100644 index 0000000..d94fad4 --- /dev/null +++ b/src/Sphagnum.Common/Services/ChannelsPool.cs @@ -0,0 +1,36 @@ +using System.Collections.Concurrent; +using System.Threading.Channels; + +namespace Sphagnum.Common.Services +{ + internal class ChannelsPool + { + private readonly int _poolSize; + public ChannelsPool(int poolSize) + { + _poolSize = poolSize; + } + + private readonly ConcurrentQueue> channels = new ConcurrentQueue>(); + + public Channel Get() + { + if (channels.TryDequeue(out var channel)) + { + return channel; + } + else + { + return Channel.CreateBounded(1); + } + } + + public void Return(Channel channel) + { + if (channels.Count < _poolSize) + { + channels.Enqueue(channel); + } + } + } +} diff --git a/src/Sphagnum.Common/Services/SocketConnection.cs b/src/Sphagnum.Common/Services/SocketConnection.cs new file mode 100644 index 0000000..7f11198 --- /dev/null +++ b/src/Sphagnum.Common/Services/SocketConnection.cs @@ -0,0 +1,62 @@ +using Sphagnum.Common.Contracts.Infrastructure; +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Sphagnum.Common.Services +{ + internal class SocketConnection : IConnection + { + private readonly Socket _socket; + + public SocketConnection(Socket socket) + { + _socket = socket; + } + + public bool Connected => _socket.Connected; + + public async Task AcceptAsync() + { + var socket = await _socket.AcceptAsync(); + return new SocketConnection(socket); + } + + public void Bind(EndPoint endPoint) + { + _socket.Bind(endPoint); + } + + public void Close() + { + _socket.Close(); + } + + public Task ConnectAsync(string host, int port) + { + return _socket.ConnectAsync(host, port); + } + + public void Dispose() + { + _socket.Dispose(); + } + + public void Listen(int backlog) + { + _socket.Listen(backlog); + } + + public ValueTask ReceiveAsync(Memory buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default) + { + return _socket.ReceiveAsync(buffer, socketFlags, cancellationToken); + } + + public ValueTask SendAsync(ReadOnlyMemory buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default) + { + return _socket.SendAsync(buffer, socketFlags, cancellationToken); + } + } +} diff --git a/src/Sphagnum.Common/Services/SphagnumConnection.cs b/src/Sphagnum.Common/Services/SphagnumConnection.cs new file mode 100644 index 0000000..ffb058c --- /dev/null +++ b/src/Sphagnum.Common/Services/SphagnumConnection.cs @@ -0,0 +1,143 @@ +using Sphagnum.Common.Contracts.Infrastructure; +using Sphagnum.Common.Exceptions; +using Sphagnum.Common.Utils; +using Sphagnum.Common.Utils.Models; +using System; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; + +namespace Sphagnum.Common.Services +{ + internal class SphagnumConnection + { + protected readonly IConnection _connection; + private readonly ChannelsPool _pool = new ChannelsPool(100); + private readonly ConcurrentDictionary> sendingItems = new ConcurrentDictionary>(); + private readonly Func> _messagesProcessorFactory; + private readonly Func _messagesProcessor; + + public SphagnumConnection(Func connectionsFactory, Func> messagesProcessorFactory) + { + _connection = connectionsFactory(); // new SocketConnection(new Socket(SocketType.Stream, ProtocolType.Tcp)); + _messagesProcessorFactory = messagesProcessorFactory; + _messagesProcessor = _messagesProcessorFactory(); + RecievingTask(); + } + + private SphagnumConnection(IConnection socket, Func> messagesProcessorFactory) + { + _connection = socket; + _messagesProcessorFactory = messagesProcessorFactory; + _messagesProcessor = _messagesProcessorFactory(); + } + + public bool Connected => _connection.Connected; + + public Task ConnectAsync(string host, int port) + { + return _connection.ConnectAsync(host, port); + } + + public async virtual Task AcceptAsync() + { + var socket = await _connection.AcceptAsync(); + return new SphagnumConnection(socket, _messagesProcessorFactory); + } + + public void Bind(int port) + { + _connection.Bind(new IPEndPoint(IPAddress.Any, port)); + } + + public void Close() + { + _connection.Close(); + } + + public void Dispose() + { + _connection.Dispose(); + } + + public void Listen(int backlog) + { + _connection.Listen(backlog); + } + + public async ValueTask SendAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + int res; + if (buffer.Span[4] != (byte)MessageType.MessageAccepted) + { + var channel = _pool.Get(); + sendingItems.TryAdd(MessageParser.GetMessageId(buffer), channel); + res = await _connection.SendAsync(buffer, SocketFlags.None, cancellationToken); + var message = await channel.Reader.WaitToReadAsync(cancellationToken);// todo обработка сообщения + _pool.Return(channel); + return res; + } + else + { + res = await _connection.SendAsync(buffer, SocketFlags.None, cancellationToken); + } + return res; + } + + private async ValueTask ReceiveAsync(CancellationToken cancellationToken = default) + { + var lengthBuffer = new byte[4]; + await _connection.ReceiveAsync(lengthBuffer, SocketFlags.Peek, cancellationToken); + var length = BitConverter.ToInt32(lengthBuffer, 0); + var result = new byte[length]; + await _connection.ReceiveAsync(result, SocketFlags.None, cancellationToken); + return result; + } + + private async Task RecievingTask(CancellationToken token = default) + { + var successRespBuffer = new byte[23]; + successRespBuffer[4] = (byte)MessageType.MessageAccepted; + successRespBuffer[0] = 23; + while (Connected) + { + try + { + var message = await ReceiveAsync(token); + if (message[4] != (byte)MessageType.MessageAccepted) + { + try + { + await _messagesProcessor(message); + MessageParser.CopyId(message, successRespBuffer); + await SendAsync(successRespBuffer, token); + } + catch (AuthException ex) + { + await SendAsync(successRespBuffer, token); + await Task.Delay(1000); + _connection.Close(); + } + catch (Exception ex) + { + + } + } + else if (sendingItems.TryRemove(MessageParser.GetMessageId(message), out var channel)) + { + await channel.Writer.WriteAsync(message); + } + } + catch (Exception ex) + { + + } + } + _connection.Close(); + _connection.Dispose(); + } + } +} diff --git a/src/Sphagnum.Common/Sphagnum.Common.csproj b/src/Sphagnum.Common/Sphagnum.Common.csproj new file mode 100644 index 0000000..0afcf1e --- /dev/null +++ b/src/Sphagnum.Common/Sphagnum.Common.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.1 + enable + + + + + + + diff --git a/src/Sphagnum.Common/Utils/Enums/MessageFlags.cs b/src/Sphagnum.Common/Utils/Enums/MessageFlags.cs new file mode 100644 index 0000000..adf19aa --- /dev/null +++ b/src/Sphagnum.Common/Utils/Enums/MessageFlags.cs @@ -0,0 +1,13 @@ +using System; + +namespace Sphagnum.Common.Utils.Models +{ + [Flags] + internal enum MessageFlags : ushort + { + None = 0, + HasRoutingKey = 1, + HasPayload = 2, + HasExchange = 4, + } +} diff --git a/src/Sphagnum.Common/Utils/Enums/MessageType.cs b/src/Sphagnum.Common/Utils/Enums/MessageType.cs new file mode 100644 index 0000000..4821119 --- /dev/null +++ b/src/Sphagnum.Common/Utils/Enums/MessageType.cs @@ -0,0 +1,18 @@ +namespace Sphagnum.Common.Utils.Models +{ + internal enum MessageType : byte + { + Unknown = 0, + Auth = 1, + + AuthSuccessfull = 2, + AuthSuccessFailed = 3, + + Common = 4, + Ack = 6, + Nack = 7, + Reject = 8, + + MessageAccepted = 5, + } +} diff --git a/src/Sphagnum.Common/Utils/HashCalculator.cs b/src/Sphagnum.Common/Utils/HashCalculator.cs new file mode 100644 index 0000000..f88ccb8 --- /dev/null +++ b/src/Sphagnum.Common/Utils/HashCalculator.cs @@ -0,0 +1,14 @@ +using System.Security.Cryptography; +using System.Text; + +namespace Sphagnum.Common.Utils +{ + internal static class HashCalculator + { + private readonly static SHA256 _hash = SHA256.Create(); + public static byte[] Calc(string text) + { + return _hash.ComputeHash(Encoding.UTF8.GetBytes(text)); + } + } +} diff --git a/src/Sphagnum.Common/Utils/MessageParser.cs b/src/Sphagnum.Common/Utils/MessageParser.cs new file mode 100644 index 0000000..11b6dcd --- /dev/null +++ b/src/Sphagnum.Common/Utils/MessageParser.cs @@ -0,0 +1,291 @@ +using Sphagnum.Common.Contracts.Messaging; +using Sphagnum.Common.Contracts.Messaging.Messages; +using Sphagnum.Common.Utils.Models; +using System; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Sphagnum.Common.Utils +{ + /// + /// Порядок передачи: + /// 1. MessageSize, 4 байта + /// 2. MessageType, 1 байт + /// 3. MessageFlags, 2 байта + /// 4. Id сообщения, если есть, 16 байт + /// 5. ExchangeNameLength, если есть, 1 байт + /// 6. ExchangeName, если есть, ExchangeNameLength байт, Utf8 + /// 7. RoutingKey, если есть, 3 байта + /// 8. PayloadSize, если есть - 4 байта + /// 9. Payload, если есть, PayloadSize байт + /// + internal static class MessageParser + { + public static OutgoingMessage UnpackOutgoingMessage(byte[] bytes) + { + if ((MessageType)bytes[4] != MessageType.Common) + { + throw new ArgumentException("Uncorrect message type! 1 (MessageType.Common) expected!"); + } + var exchangeName = GetExchangeName(bytes); + var routingKey = GetRoutingKey(bytes); + var payload = GetPayload(bytes); + return new OutgoingMessage(exchangeName, routingKey, payload); + } + + public static IncommingMessage UnpackIncomingMessage(byte[] bytes) + { + if ((MessageType)bytes[4] != MessageType.Common) + { + throw new ArgumentException("Uncorrect message type! 1 (MessageType.Common) expected!"); + } + var id = GetMessageId(bytes); + var payload = GetPayload(bytes); + return new IncommingMessage(id, payload); + } + + public static byte[] PackMessage(IncommingMessage message) + { + var result = new byte[27 + message.Payload.Length]; + result[4] = (byte)MessageType.Common; + var flags = MessageFlags.HasPayload; + BitConverter.TryWriteBytes(result.AsSpan(5, 2), (ushort)flags); + message.MessageId.TryWriteBytes(result.AsSpan(7)); + BitConverter.TryWriteBytes(result.AsSpan(23), message.Payload.Length); + message.Payload.CopyTo(result.AsMemory(27)); + BitConverter.TryWriteBytes(result.AsSpan(0, 4), result.Length); + return result; + } + + public static byte[] PackReplyMessage(MessageType messageType) + { + var res = new byte[23]; + res[4] = (byte)messageType; + res[0] = 23; + return res; + } + + public static byte[] PackReplyMessage(MessageType messageType, Guid parentMessageId) + { + var res = new byte[23]; + res[4] = (byte)messageType; + res[0] = 23; + parentMessageId.TryWriteBytes(res.AsSpan(7)); + return res; + } + + public static byte[] PackMessage(AuthMessage message) + { + var result = new byte[27 + message.Payload.Length]; + result[4] = (byte)MessageType.Auth; + var flags = MessageFlags.HasPayload; + BitConverter.TryWriteBytes(result.AsSpan(5, 2), (ushort)flags); + message.MessageId.TryWriteBytes(result.AsSpan(7)); + BitConverter.TryWriteBytes(result.AsSpan(23), message.Payload.Length); + message.Payload.CopyTo(result.AsMemory(27)); + BitConverter.TryWriteBytes(result.AsSpan(0, 4), result.Length); + return result; + } + + public static byte[] PackMessage(OutgoingMessage message) + { + if (string.IsNullOrEmpty(message.Exchange) || string.IsNullOrWhiteSpace(message.Exchange)) + { + throw new ArgumentException("Bad exchange name!"); + } + else if (Encoding.UTF8.GetByteCount(message.Exchange) > 255) + { + throw new ArgumentException("Exchange name in UTF8 encoding must allocate < 256 bytes!"); + } + + var flags = MessageFlags.HasExchange; + int count = 23; + if (message.Payload.Length > 0) + { + flags |= MessageFlags.HasPayload; + count += message.Payload.Length; + count += 4; + } + if (!message.RoutingKey.IsEmpry) + { + flags |= MessageFlags.HasRoutingKey; + count += 3; + } + + var exchangeNameBytes = Encoding.UTF8.GetBytes(message.Exchange);// todo перевести на более оптимальный метод, не аллоцирующий лишнего. + count += exchangeNameBytes.Length; + count++; + return Pack(message, Guid.NewGuid(), flags, count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Guid GetMessageId(byte[] bytes) + { + var slice = bytes.AsSpan(7, 16); + return new Guid(slice); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Guid GetMessageId(Span bytes) + { + var slice = bytes.Slice(7, 16); + return new Guid(slice); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Guid GetMessageId(ReadOnlyMemory bytes) + { + var slice = bytes.Slice(7, 16); + return new Guid(slice.Span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void CopyId(ReadOnlySpan bytes, byte[] buffer) + { + var slice = bytes.Slice(7, 16); + slice.CopyTo(buffer.AsSpan(7, 16)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static MessageType GetMessageType(Span bytes) + { + return (MessageType)bytes[4]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static byte[] GetPayload(Span bytes) + { + var result = Array.Empty(); + if (HasPayload(bytes)) + { + var shift = 23; + if (HasExchange(bytes))//todo проверить бенчмарком, как работает инлайн + { + shift += bytes[23]; + shift++; + } + if (HasKey(bytes)) + { + shift += 3; + } + + var payloadSize = BitConverter.ToInt32(bytes[shift..]); + if (payloadSize > 0) + { + result = bytes.Slice(shift + 4, payloadSize).ToArray(); + } + } + return result; + + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int GetPayloadStart(Span bytes) + { + if (HasPayload(bytes)) + { + var shift = 27; + if (HasExchange(bytes))//todo проверить бенчмарком, как работает инлайн + { + shift += bytes[23]; + shift += 1; + } + if (HasKey(bytes)) + { + shift += 3; + } + return shift; + } + return -1; + } + + internal static byte[] Pack(OutgoingMessage message, Guid id, MessageFlags flags, int count) + { + var result = new byte[count]; + result[4] = (byte)MessageType.Common; + var shift = 5; + BitConverter.TryWriteBytes(result.AsSpan(shift), (ushort)flags);//2. flags + shift += 2; + id.TryWriteBytes(result.AsSpan(shift));//3. id + shift += 16; + if ((flags & MessageFlags.HasExchange) == MessageFlags.HasExchange) + { + var exchangeBytes = Encoding.UTF8.GetBytes(message.Exchange); + BitConverter.TryWriteBytes(result.AsSpan(shift), (byte)(message.Exchange.Length));//4. ExchangeNameLength + shift += 1; + exchangeBytes.CopyTo(result.AsSpan(shift));//5. ExchangeName + shift += exchangeBytes.Length; + } + if ((flags & MessageFlags.HasRoutingKey) == MessageFlags.HasRoutingKey)//6. RoutingKey + { + result[shift] = message.RoutingKey.Part1; + shift++; + result[shift] = message.RoutingKey.Part2; + shift++; + result[shift] = message.RoutingKey.Part3; + shift++; + } + if ((flags & MessageFlags.HasPayload) == MessageFlags.HasPayload) + { + BitConverter.TryWriteBytes(result.AsSpan(shift), message.Payload.Length);//7. PayloadSize + shift += 4; + message.Payload.CopyTo(result.AsMemory(shift));//8. Payload + } + BitConverter.TryWriteBytes(result.AsSpan(0, 4), result.Length); + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool HasKey(Span bytes) + { + var value = BitConverter.ToUInt16(bytes.Slice(5, 2)); + return (((MessageFlags)value & MessageFlags.HasRoutingKey) == MessageFlags.HasRoutingKey); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool HasPayload(Span bytes) + { + var value = BitConverter.ToUInt16(bytes.Slice(5, 2)); + return (((MessageFlags)value & MessageFlags.HasPayload) == MessageFlags.HasPayload); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool HasExchange(Span bytes) + { + var value = BitConverter.ToUInt16(bytes.Slice(5, 2)); + return (((MessageFlags)value & MessageFlags.HasExchange) == MessageFlags.HasExchange); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string GetExchangeName(Span bytes) + { + var hasExchange = HasExchange(bytes); + if (!hasExchange) + { + throw new ArgumentException("bytes must contains exchange name!"); + } + return Encoding.UTF8.GetString(bytes.Slice(24, bytes[23])); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static RoutingKey GetRoutingKey(Span bytes) + { + var length = bytes[23]; + RoutingKey key; + if (HasKey(bytes)) + { + var routingKeyShift = 23 + length + 1; + + var routingKeyPart1 = bytes[routingKeyShift]; + var routingKeyPart2 = bytes[routingKeyShift + 1]; + var routingKeyPart3 = bytes[routingKeyShift + 2]; + key = new RoutingKey(routingKeyPart1, routingKeyPart2, routingKeyPart3); + } + else + { + key = new RoutingKey(); + } + return key; + } + } +} diff --git a/src/Sphagnum.DebugClient/Controllers/TestController.cs b/src/Sphagnum.DebugClient/Controllers/TestController.cs new file mode 100644 index 0000000..bb2cc62 --- /dev/null +++ b/src/Sphagnum.DebugClient/Controllers/TestController.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Mvc; +using Sphagnum.Common.Contracts.Messaging; +using Sphagnum.Common.Contracts.Messaging.Messages; + +namespace Sphagnum.DebugClient.Controllers +{ + [ApiController] + [Route("[controller]/[action]")] + public class TestController : ControllerBase + { + private readonly IMessagingClient _connection; + private static readonly Task? rec; + + public TestController(IMessagingClient connection) + { + _connection = connection; + } + + [HttpGet] + public string test() + { + return "Ok!"; + } + + + [HttpGet] + public async Task Send(int size) + { + var payload1 = new byte[size]; + var payload2 = new byte[size]; + + for (int i = 0; i < size; i++) + { + payload1[i] = 1; + payload2[i] = 2; + } + var t1 = _connection.Publish(new OutgoingMessage("test", payload1)).AsTask(); + var t2 = _connection.Publish(new OutgoingMessage("test", payload2)).AsTask(); + await Task.WhenAll(t1, t2); + } + } +} diff --git a/src/Sphagnum.DebugClient/Dockerfile b/src/Sphagnum.DebugClient/Dockerfile new file mode 100644 index 0000000..66a1437 --- /dev/null +++ b/src/Sphagnum.DebugClient/Dockerfile @@ -0,0 +1,27 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Sphagnum.DebugClient/Sphagnum.DebugClient.csproj", "Sphagnum.DebugClient/"] +COPY ["Sphagnum.Client/Sphagnum.Client.csproj", "Sphagnum.Client/"] +COPY ["Sphagnum.Common/Sphagnum.Common.csproj", "Sphagnum.Common/"] +COPY ["Sphagnum.Common.Contracts/Sphagnum.Common.Contracts.csproj", "Sphagnum.Common.Contracts/"] +RUN dotnet restore "./Sphagnum.DebugClient/./Sphagnum.DebugClient.csproj" +COPY . . +WORKDIR "/src/Sphagnum.DebugClient" +RUN dotnet build "./Sphagnum.DebugClient.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Sphagnum.DebugClient.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Sphagnum.DebugClient.dll"] \ No newline at end of file diff --git a/src/Sphagnum.DebugClient/Program.cs b/src/Sphagnum.DebugClient/Program.cs new file mode 100644 index 0000000..ccc6299 --- /dev/null +++ b/src/Sphagnum.DebugClient/Program.cs @@ -0,0 +1,26 @@ +using Sphagnum.Client; +using Sphagnum.Common.Contracts.Login; +using Sphagnum.Common.Contracts.Messaging; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddSingleton(new ConnectionFactory() +{ + UserRights = UserRights.All, + Login = "root", + Password = "root", + Hostname = "test_server", + Port = 8081, +}); +builder.Services.AddSingleton(); +var app = builder.Build(); + +app.UseSwagger(); +app.UseSwaggerUI(); + +app.MapControllers(); + +app.Run(); diff --git a/src/Sphagnum.DebugClient/Sphagnum.DebugClient.csproj b/src/Sphagnum.DebugClient/Sphagnum.DebugClient.csproj new file mode 100644 index 0000000..4e338e0 --- /dev/null +++ b/src/Sphagnum.DebugClient/Sphagnum.DebugClient.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + true + Linux + ..\docker-compose.dcproj + + + + + + + + + + + + diff --git a/src/Sphagnum.DebugClient/appsettings.Development.json b/src/Sphagnum.DebugClient/appsettings.Development.json new file mode 100644 index 0000000..ff66ba6 --- /dev/null +++ b/src/Sphagnum.DebugClient/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Sphagnum.DebugClient/appsettings.json b/src/Sphagnum.DebugClient/appsettings.json new file mode 100644 index 0000000..4d56694 --- /dev/null +++ b/src/Sphagnum.DebugClient/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/Sphagnum.DebugService/Dockerfile b/src/Sphagnum.DebugService/Dockerfile new file mode 100644 index 0000000..195cf2d --- /dev/null +++ b/src/Sphagnum.DebugService/Dockerfile @@ -0,0 +1,27 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Sphagnum.DebugService/Sphagnum.DebugService.csproj", "Sphagnum.DebugService/"] +COPY ["Sphagnum.Server/Sphagnum.Server.csproj", "Sphagnum.Server/"] +COPY ["Sphagnum.Common/Sphagnum.Common.csproj", "Sphagnum.Common/"] +COPY ["Sphagnum.Common.Contracts/Sphagnum.Common.Contracts.csproj", "Sphagnum.Common.Contracts/"] +RUN dotnet restore "./Sphagnum.DebugService/./Sphagnum.DebugService.csproj" +COPY . . +WORKDIR "/src/Sphagnum.DebugService" +RUN dotnet build "./Sphagnum.DebugService.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Sphagnum.DebugService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Sphagnum.DebugService.dll"] \ No newline at end of file diff --git a/src/Sphagnum.DebugService/Program.cs b/src/Sphagnum.DebugService/Program.cs new file mode 100644 index 0000000..ba6e133 --- /dev/null +++ b/src/Sphagnum.DebugService/Program.cs @@ -0,0 +1,12 @@ +using Sphagnum.Common.Contracts.Login; +using Sphagnum.Server; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddControllers(); +builder.Services.AddSingleton(); +builder.Services.AddHostedService(); + +var app = builder.Build(); +app.MapControllers(); + +app.Run(); diff --git a/src/Sphagnum.DebugService/Sphagnum.DebugService.csproj b/src/Sphagnum.DebugService/Sphagnum.DebugService.csproj new file mode 100644 index 0000000..5856a8c --- /dev/null +++ b/src/Sphagnum.DebugService/Sphagnum.DebugService.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + true + Linux + ..\..\src + ..\docker-compose.dcproj + + + + + + + + + + + diff --git a/src/Sphagnum.DebugService/appsettings.Development.json b/src/Sphagnum.DebugService/appsettings.Development.json new file mode 100644 index 0000000..ff66ba6 --- /dev/null +++ b/src/Sphagnum.DebugService/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Sphagnum.DebugService/appsettings.json b/src/Sphagnum.DebugService/appsettings.json new file mode 100644 index 0000000..4d56694 --- /dev/null +++ b/src/Sphagnum.DebugService/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/Sphagnum.Server.FuncTests/GlobalUsings.cs b/src/Sphagnum.Server.FuncTests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/src/Sphagnum.Server.FuncTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/src/Sphagnum.Server.FuncTests/Sphagnum.Server.FuncTests.csproj b/src/Sphagnum.Server.FuncTests/Sphagnum.Server.FuncTests.csproj new file mode 100644 index 0000000..b6709d9 --- /dev/null +++ b/src/Sphagnum.Server.FuncTests/Sphagnum.Server.FuncTests.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + diff --git a/src/Sphagnum.Server.FuncTests/UnitTest1.cs b/src/Sphagnum.Server.FuncTests/UnitTest1.cs new file mode 100644 index 0000000..513c659 --- /dev/null +++ b/src/Sphagnum.Server.FuncTests/UnitTest1.cs @@ -0,0 +1,39 @@ +using Sphagnum.Client; +using Sphagnum.Common.Contracts.Login; +using Sphagnum.Server.Broker.Services; + +namespace Sphagnum.Server.FuncTests +{ + public class Tests + { + private BrokerDefaultBase? server; + private ClientDefault? client1; + [SetUp] + public async Task Setup() + { + var connectionFactory = new ConnectionFactory() + { + Hostname = "localhost", + Port = 8081, + Login = "root", + Password = "root", + UserRights = UserRights.All, + }; + server = BrokerDefaultBase.Create(connectionFactory); + await server.StartAsync(connectionFactory.Port); + client1 = new ClientDefault(connectionFactory); + } + + [Test] + public async Task Test1() + { + await client1.Publish(new Common.Contracts.Messaging.Messages.OutgoingMessage("111", new byte[3] { 3, 3, 3 })); + } + + [Test] + public async Task Test2() + { + await client1.Publish(new Common.Contracts.Messaging.Messages.OutgoingMessage("111", new byte[3] { 3, 3, 3 })); + } + } +} \ No newline at end of file diff --git a/src/Sphagnum.Server/AssemblySettings.cs b/src/Sphagnum.Server/AssemblySettings.cs new file mode 100644 index 0000000..a455e20 --- /dev/null +++ b/src/Sphagnum.Server/AssemblySettings.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Sphagnum.Server.FuncTests")] \ No newline at end of file diff --git a/src/Sphagnum.Server/Broker/Services/BrokerDefaultBase.cs b/src/Sphagnum.Server/Broker/Services/BrokerDefaultBase.cs new file mode 100644 index 0000000..a15e3f8 --- /dev/null +++ b/src/Sphagnum.Server/Broker/Services/BrokerDefaultBase.cs @@ -0,0 +1,65 @@ +using Sphagnum.Common.Contracts.Login; +using Sphagnum.Common.Services; +using Sphagnum.Server.Cluster.Contracts; +using Sphagnum.Server.Cluster.Services; +using Sphagnum.Server.DataProcessing.Contracts; +using Sphagnum.Server.DataProcessing.Services; +using Sphagnum.Server.Storage.Messages.Contracts; +using Sphagnum.Server.Storage.Messages.Services; +using Sphagnum.Server.Storage.Users.Contracts; +using Sphagnum.Server.Storage.Users.Services; + +namespace Sphagnum.Server.Broker.Services +{ + internal class BrokerDefaultBase(ConnectionFactory connectionFactory, IMessagesStorage messagesStorage, IDistributor distributor, IDataProcessor dataProcessor) + { + private readonly SphagnumConnection _connection; + private readonly CancellationTokenSource _cts = new(); + private Task? _acceptationTask; + + private readonly IAuthInfoStorage _authInfoStorage = new AuthInfoStorageBase(); + private readonly IMessagesStorage _messagesStorage = messagesStorage; + private readonly IDistributor _distributor = distributor; + private readonly IDataProcessor _dataProcessor = dataProcessor; + private readonly ConnectionFactory _connectionFactory = connectionFactory; + + public Task StartAsync(int port) + { + _connectionFactory.CreateDefault(() => + { + var processor = new MessagesProcessor(_authInfoStorage, _messagesStorage, _distributor, _dataProcessor); + return processor.ProcessMessage; + }); + _connection.Bind(port); + _connection.Listen(1000); //todo разобраться что делает этот параметр. + _acceptationTask = AcceptationWorker(_cts.Token); + return Task.CompletedTask; + } + + public Task StopAsync() + { + _cts.Cancel(); + return Task.CompletedTask; + } + + internal static BrokerDefaultBase Create(ConnectionFactory connectionFactory) + { + return new BrokerDefaultBase( + connectionFactory, + new MessagesStorageDefault(), + new DistributorDefault(), + new DataProcessorDefault() + ); + } + + + private async Task AcceptationWorker(CancellationToken token) + { + + while (!token.IsCancellationRequested) + { + var acceptedSocket = await _connection.AcceptAsync(); + } + } + } +} diff --git a/src/Sphagnum.Server/Broker/Services/BrokerHost.cs b/src/Sphagnum.Server/Broker/Services/BrokerHost.cs new file mode 100644 index 0000000..4149cfb --- /dev/null +++ b/src/Sphagnum.Server/Broker/Services/BrokerHost.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Hosting; +using Sphagnum.Common.Contracts.Login; +using Sphagnum.Server.Broker.Services; + +namespace Sphagnum.Server +{ + public class BrokerHost : IHostedService + { + private readonly BrokerDefaultBase _broker; + + public BrokerHost(ConnectionFactory connectionFactory) + { + _broker = BrokerDefaultBase.Create(connectionFactory); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + return _broker.StartAsync(8081); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return _broker.StopAsync(); + } + } +} diff --git a/src/Sphagnum.Server/Broker/Services/MessagesProcessor.cs b/src/Sphagnum.Server/Broker/Services/MessagesProcessor.cs new file mode 100644 index 0000000..61f7907 --- /dev/null +++ b/src/Sphagnum.Server/Broker/Services/MessagesProcessor.cs @@ -0,0 +1,68 @@ +using Sphagnum.Common; +using Sphagnum.Common.Contracts.Login; +using Sphagnum.Common.Exceptions; +using Sphagnum.Common.Utils; +using Sphagnum.Common.Utils.Models; +using Sphagnum.Server.Cluster.Contracts; +using Sphagnum.Server.DataProcessing.Contracts; +using Sphagnum.Server.Storage.Messages.Contracts; +using Sphagnum.Server.Storage.Users.Contracts; + +namespace Sphagnum.Server.Broker.Services +{ + internal class MessagesProcessor + { + private bool AuthOk = true; + + private readonly IAuthInfoStorage _authInfoStorage; + private readonly IMessagesStorage _messagesStorage; + private readonly IDistributor _distributor; + private readonly IDataProcessor _dataProcessor; + public MessagesProcessor(IAuthInfoStorage authInfoStorage, IMessagesStorage messagesStorage, IDistributor distributor, IDataProcessor dataProcessor) + { + _authInfoStorage = authInfoStorage; + _messagesStorage = messagesStorage; + _distributor = distributor; + _dataProcessor = dataProcessor; + } + + internal async Task ProcessMessage(byte[] message) + { + if (AuthOk) + { + await _messagesStorage.LogMessage(message); + await _distributor.DistributeData(message); + await _dataProcessor.PutMessage(message); + } + else if (await CheckRights(message)) + { + AuthOk = true; + await _messagesStorage.LogMessage(message); + await _distributor.DistributeData(message); + await _dataProcessor.PutMessage(message); + } + else + { + throw new AuthException(); + } + } + + private async ValueTask CheckRights(byte[] buffer) + { + var messageType = MessageParser.GetMessageType(buffer); + if (messageType == MessageType.Auth) + { + var payloadStart = MessageParser.GetPayloadStart(buffer); + var rights = (UserRights)BitConverter.ToInt16(buffer.AsSpan(Constants.HashedUserDataSizeInfBytes + Constants.HashedUserDataSizeInfBytes + payloadStart, 2)); + var isRecievingAllowed = await _authInfoStorage.CheckRights( + buffer.AsSpan(payloadStart, Constants.HashedUserDataSizeInfBytes), + buffer.AsSpan(payloadStart + Constants.HashedUserDataSizeInfBytes, Constants.HashedUserDataSizeInfBytes), + rights, + new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token + ); + return isRecievingAllowed; + } + return false; + } + } +} diff --git a/src/Sphagnum.Server/Cluster/Contracts/IDistributor.cs b/src/Sphagnum.Server/Cluster/Contracts/IDistributor.cs new file mode 100644 index 0000000..34c1ca8 --- /dev/null +++ b/src/Sphagnum.Server/Cluster/Contracts/IDistributor.cs @@ -0,0 +1,7 @@ +namespace Sphagnum.Server.Cluster.Contracts +{ + public interface IDistributor + { + ValueTask DistributeData(ReadOnlySpan data); + } +} diff --git a/src/Sphagnum.Server/Cluster/Services/DistributorDefault.cs b/src/Sphagnum.Server/Cluster/Services/DistributorDefault.cs new file mode 100644 index 0000000..0f086cb --- /dev/null +++ b/src/Sphagnum.Server/Cluster/Services/DistributorDefault.cs @@ -0,0 +1,12 @@ +using Sphagnum.Server.Cluster.Contracts; + +namespace Sphagnum.Server.Cluster.Services +{ + internal class DistributorDefault : IDistributor + { + public ValueTask DistributeData(ReadOnlySpan data) + { + return ValueTask.CompletedTask; + } + } +} diff --git a/src/Sphagnum.Server/DataProcessing/Contracts/IDataProcessor.cs b/src/Sphagnum.Server/DataProcessing/Contracts/IDataProcessor.cs new file mode 100644 index 0000000..18dbdaf --- /dev/null +++ b/src/Sphagnum.Server/DataProcessing/Contracts/IDataProcessor.cs @@ -0,0 +1,9 @@ +namespace Sphagnum.Server.DataProcessing.Contracts +{ + internal interface IDataProcessor + { + public bool RegisterCoprocessor(string key, Func func); + public Func? UnregisterCoprocessor(string key); + public ValueTask PutMessage(ReadOnlySpan message); + } +} diff --git a/src/Sphagnum.Server/DataProcessing/Services/DataProcessorDefault.cs b/src/Sphagnum.Server/DataProcessing/Services/DataProcessorDefault.cs new file mode 100644 index 0000000..99fe957 --- /dev/null +++ b/src/Sphagnum.Server/DataProcessing/Services/DataProcessorDefault.cs @@ -0,0 +1,29 @@ +using Sphagnum.Server.DataProcessing.Contracts; +using System.Collections.Concurrent; + +namespace Sphagnum.Server.DataProcessing.Services +{ + internal class DataProcessorDefault : IDataProcessor + { + private readonly ConcurrentDictionary> _processors = new(); + + public ValueTask PutMessage(ReadOnlySpan message) + { + return ValueTask.CompletedTask; + } + + public bool RegisterCoprocessor(string key, Func func) + { + return _processors.TryAdd(key, func); + } + + public Func? UnregisterCoprocessor(string key) + { + if (_processors.TryRemove(key, out Func? res)) + { + return res; + } + return null; + } + } +} diff --git a/src/Sphagnum.Server/Sphagnum.Server.csproj b/src/Sphagnum.Server/Sphagnum.Server.csproj new file mode 100644 index 0000000..802859c --- /dev/null +++ b/src/Sphagnum.Server/Sphagnum.Server.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/src/Sphagnum.Server/Storage/Messages/Contracts/IMessagesStorage.cs b/src/Sphagnum.Server/Storage/Messages/Contracts/IMessagesStorage.cs new file mode 100644 index 0000000..f8e8671 --- /dev/null +++ b/src/Sphagnum.Server/Storage/Messages/Contracts/IMessagesStorage.cs @@ -0,0 +1,7 @@ +namespace Sphagnum.Server.Storage.Messages.Contracts +{ + internal interface IMessagesStorage + { + ValueTask LogMessage(ReadOnlyMemory message); + } +} diff --git a/src/Sphagnum.Server/Storage/Messages/Services/MessagesStorageDefault.cs b/src/Sphagnum.Server/Storage/Messages/Services/MessagesStorageDefault.cs new file mode 100644 index 0000000..ecce616 --- /dev/null +++ b/src/Sphagnum.Server/Storage/Messages/Services/MessagesStorageDefault.cs @@ -0,0 +1,12 @@ +using Sphagnum.Server.Storage.Messages.Contracts; + +namespace Sphagnum.Server.Storage.Messages.Services +{ + internal class MessagesStorageDefault : IMessagesStorage + { + public ValueTask LogMessage(ReadOnlyMemory message) + { + return ValueTask.CompletedTask; + } + } +} diff --git a/src/Sphagnum.Server/Storage/Users/Contracts/IAuthInfoStorage.cs b/src/Sphagnum.Server/Storage/Users/Contracts/IAuthInfoStorage.cs new file mode 100644 index 0000000..f2bae76 --- /dev/null +++ b/src/Sphagnum.Server/Storage/Users/Contracts/IAuthInfoStorage.cs @@ -0,0 +1,13 @@ +using Sphagnum.Common.Contracts.Login; + +namespace Sphagnum.Server.Storage.Users.Contracts +{ + internal interface IAuthInfoStorage + { + public ValueTask CheckRights(Span hashedUsername, Span hashedPassword, UserRights userRights, CancellationToken token = default); + + public ValueTask AddUser(Span hashedUsername, Span hashedPassword, UserRights userRights); + + public ValueTask SetRights(Span hashedUsername, UserRights userRights); + } +} diff --git a/src/Sphagnum.Server/Storage/Users/Services/AuthInfoStorageBase.cs b/src/Sphagnum.Server/Storage/Users/Services/AuthInfoStorageBase.cs new file mode 100644 index 0000000..a521abd --- /dev/null +++ b/src/Sphagnum.Server/Storage/Users/Services/AuthInfoStorageBase.cs @@ -0,0 +1,30 @@ +using Sphagnum.Common.Contracts.Login; +using Sphagnum.Common.Utils; +using Sphagnum.Server.Storage.Users.Contracts; +using System.Numerics; + +namespace Sphagnum.Server.Storage.Users.Services +{ + internal class AuthInfoStorageBase : IAuthInfoStorage + { + private readonly Vector RootUserLogin = new(HashCalculator.Calc("root")); + private readonly Vector RootUserPassword = new(HashCalculator.Calc("root")); + private readonly UserRights RootUserRights = UserRights.All; + public ValueTask AddUser(Span hashedUsername, Span hashedPassword, UserRights userRights) + { + return ValueTask.CompletedTask; + } + + public ValueTask CheckRights(Span hashedUsername, Span hashedPassword, UserRights userRights, CancellationToken token = default) + { + var username = new Vector(hashedUsername); + var pwd = new Vector(hashedPassword); + return ValueTask.FromResult(username == RootUserLogin && pwd == RootUserPassword && (userRights & RootUserRights) == userRights); + } + + public ValueTask SetRights(Span hashedUsername, UserRights userRights) + { + return ValueTask.CompletedTask; + } + } +} diff --git a/src/Sphagnum.Service/Controllers/TestController.cs b/src/Sphagnum.Service/Controllers/TestController.cs new file mode 100644 index 0000000..001e09e --- /dev/null +++ b/src/Sphagnum.Service/Controllers/TestController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Sphagnum.Service.Controllers +{ + [ApiController] + [Route("[controller]")] + public class TestController : ControllerBase + { + [HttpGet] + public string Get(string text) + { + return text; + } + } +} diff --git a/src/Sphagnum.Service/Program.cs b/src/Sphagnum.Service/Program.cs new file mode 100644 index 0000000..3a26f6f --- /dev/null +++ b/src/Sphagnum.Service/Program.cs @@ -0,0 +1,14 @@ +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +app.UseSwagger(); +app.UseSwaggerUI(); + +app.MapControllers(); + +app.Run(); diff --git a/src/Sphagnum.Service/Sphagnum.Service.csproj b/src/Sphagnum.Service/Sphagnum.Service.csproj new file mode 100644 index 0000000..2f89b26 --- /dev/null +++ b/src/Sphagnum.Service/Sphagnum.Service.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + diff --git a/src/Sphagnum.Service/appsettings.Development.json b/src/Sphagnum.Service/appsettings.Development.json new file mode 100644 index 0000000..ff66ba6 --- /dev/null +++ b/src/Sphagnum.Service/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Sphagnum.Service/appsettings.json b/src/Sphagnum.Service/appsettings.json new file mode 100644 index 0000000..4d56694 --- /dev/null +++ b/src/Sphagnum.Service/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/Sphagnum.sln b/src/Sphagnum.sln new file mode 100644 index 0000000..d13ede3 --- /dev/null +++ b/src/Sphagnum.sln @@ -0,0 +1,89 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sphagnum.Client", "Sphagnum.Client\Sphagnum.Client.csproj", "{0AFD5F86-EAE3-494E-B2E5-77F54EB31653}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{834EE1A0-1D4D-42BD-9F76-F5941DE404C2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sphagnum.Server", "Sphagnum.Server\Sphagnum.Server.csproj", "{2C86F0B0-B592-4F9E-A1E0-96BC453BCB2B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sphagnum.Service", "Sphagnum.Service\Sphagnum.Service.csproj", "{88E2533A-8568-440D-B8A9-05A5372129D9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DE8B3036-E476-4A92-92E3-4D0B58FC5137}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sphagnum.Common", "Sphagnum.Common\Sphagnum.Common.csproj", "{D6EACE65-A4A0-40A9-8896-0BB276471C25}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sphagnum.Common.UnitTests", "Sphagnum.Common.UnitTests\Sphagnum.Common.UnitTests.csproj", "{AD03A219-5187-446E-BB29-6BE8826EA6C1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "debug", "debug", "{84474E24-8329-4E47-B2A7-C9A968B13716}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sphagnum.DebugClient", "Sphagnum.DebugClient\Sphagnum.DebugClient.csproj", "{91045A44-09DF-4104-BB69-580C639F29B3}" +EndProject +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{832DB06C-C0F5-4608-B94F-86FDE003C420}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sphagnum.DebugService", "Sphagnum.DebugService\Sphagnum.DebugService.csproj", "{B6B57A78-D62D-429F-B647-A06B268ECE25}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sphagnum.Server.FuncTests", "Sphagnum.Server.FuncTests\Sphagnum.Server.FuncTests.csproj", "{696ED9DF-9F1A-405B-8C4B-AE56031CC349}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0AFD5F86-EAE3-494E-B2E5-77F54EB31653}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0AFD5F86-EAE3-494E-B2E5-77F54EB31653}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0AFD5F86-EAE3-494E-B2E5-77F54EB31653}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0AFD5F86-EAE3-494E-B2E5-77F54EB31653}.Release|Any CPU.Build.0 = Release|Any CPU + {2C86F0B0-B592-4F9E-A1E0-96BC453BCB2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C86F0B0-B592-4F9E-A1E0-96BC453BCB2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C86F0B0-B592-4F9E-A1E0-96BC453BCB2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C86F0B0-B592-4F9E-A1E0-96BC453BCB2B}.Release|Any CPU.Build.0 = Release|Any CPU + {88E2533A-8568-440D-B8A9-05A5372129D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88E2533A-8568-440D-B8A9-05A5372129D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88E2533A-8568-440D-B8A9-05A5372129D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88E2533A-8568-440D-B8A9-05A5372129D9}.Release|Any CPU.Build.0 = Release|Any CPU + {D6EACE65-A4A0-40A9-8896-0BB276471C25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6EACE65-A4A0-40A9-8896-0BB276471C25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6EACE65-A4A0-40A9-8896-0BB276471C25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6EACE65-A4A0-40A9-8896-0BB276471C25}.Release|Any CPU.Build.0 = Release|Any CPU + {AD03A219-5187-446E-BB29-6BE8826EA6C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD03A219-5187-446E-BB29-6BE8826EA6C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD03A219-5187-446E-BB29-6BE8826EA6C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD03A219-5187-446E-BB29-6BE8826EA6C1}.Release|Any CPU.Build.0 = Release|Any CPU + {91045A44-09DF-4104-BB69-580C639F29B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91045A44-09DF-4104-BB69-580C639F29B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91045A44-09DF-4104-BB69-580C639F29B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91045A44-09DF-4104-BB69-580C639F29B3}.Release|Any CPU.Build.0 = Release|Any CPU + {832DB06C-C0F5-4608-B94F-86FDE003C420}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {832DB06C-C0F5-4608-B94F-86FDE003C420}.Debug|Any CPU.Build.0 = Debug|Any CPU + {832DB06C-C0F5-4608-B94F-86FDE003C420}.Release|Any CPU.ActiveCfg = Release|Any CPU + {832DB06C-C0F5-4608-B94F-86FDE003C420}.Release|Any CPU.Build.0 = Release|Any CPU + {B6B57A78-D62D-429F-B647-A06B268ECE25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6B57A78-D62D-429F-B647-A06B268ECE25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6B57A78-D62D-429F-B647-A06B268ECE25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6B57A78-D62D-429F-B647-A06B268ECE25}.Release|Any CPU.Build.0 = Release|Any CPU + {696ED9DF-9F1A-405B-8C4B-AE56031CC349}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {696ED9DF-9F1A-405B-8C4B-AE56031CC349}.Debug|Any CPU.Build.0 = Debug|Any CPU + {696ED9DF-9F1A-405B-8C4B-AE56031CC349}.Release|Any CPU.ActiveCfg = Release|Any CPU + {696ED9DF-9F1A-405B-8C4B-AE56031CC349}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0AFD5F86-EAE3-494E-B2E5-77F54EB31653} = {834EE1A0-1D4D-42BD-9F76-F5941DE404C2} + {2C86F0B0-B592-4F9E-A1E0-96BC453BCB2B} = {834EE1A0-1D4D-42BD-9F76-F5941DE404C2} + {88E2533A-8568-440D-B8A9-05A5372129D9} = {834EE1A0-1D4D-42BD-9F76-F5941DE404C2} + {D6EACE65-A4A0-40A9-8896-0BB276471C25} = {834EE1A0-1D4D-42BD-9F76-F5941DE404C2} + {AD03A219-5187-446E-BB29-6BE8826EA6C1} = {DE8B3036-E476-4A92-92E3-4D0B58FC5137} + {91045A44-09DF-4104-BB69-580C639F29B3} = {84474E24-8329-4E47-B2A7-C9A968B13716} + {B6B57A78-D62D-429F-B647-A06B268ECE25} = {84474E24-8329-4E47-B2A7-C9A968B13716} + {696ED9DF-9F1A-405B-8C4B-AE56031CC349} = {DE8B3036-E476-4A92-92E3-4D0B58FC5137} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6AE6D1E4-6CC3-4D93-8498-A7E5ADC24A77} + EndGlobalSection +EndGlobal diff --git a/src/docker-compose.dcproj b/src/docker-compose.dcproj new file mode 100644 index 0000000..1bcaa56 --- /dev/null +++ b/src/docker-compose.dcproj @@ -0,0 +1,19 @@ + + + + 2.1 + Linux + False + 832db06c-c0f5-4608-b94f-86fde003c420 + LaunchBrowser + {Scheme}://localhost:{ServicePort}/swagger + sphagnum.debugclient + + + + docker-compose.yml + + + + + \ No newline at end of file diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml new file mode 100644 index 0000000..35af6b1 --- /dev/null +++ b/src/docker-compose.override.yml @@ -0,0 +1,2 @@ +version: '3.4' + diff --git a/src/docker-compose.yml b/src/docker-compose.yml new file mode 100644 index 0000000..d25d99a --- /dev/null +++ b/src/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.4' + +services: + sphagnum.debugclient: + depends_on: + - sphagnum.debugservice + image: sphagnumdebugclient + ports: + - 5000:8080 + build: + context: . + dockerfile: Sphagnum.DebugClient/Dockerfile + + sphagnum.debugservice: + hostname: test_server + image: sphagnumdebugservice + ports: + - 5001:8080 + build: + context: . + dockerfile: Sphagnum.DebugService/Dockerfile + diff --git a/src/launchSettings.json b/src/launchSettings.json new file mode 100644 index 0000000..54a5fb7 --- /dev/null +++ b/src/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "Docker Compose": { + "commandName": "DockerCompose", + "commandVersion": "1.0", + "serviceActions": { + "sphagnum.debugclient": "StartDebugging", + "sphagnum.debugservice": "StartDebugging" + } + } + } +} \ No newline at end of file