first commit

main
vladzvx 2024-04-03 00:10:11 +03:00
commit 443d4b2ea7
79 changed files with 2607 additions and 0 deletions

276
.gitignore vendored Normal file
View File

@ -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

30
src/.dockerignore Normal file
View File

@ -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/**

View File

@ -0,0 +1,13 @@
namespace Sphagnum.Client
{
//internal class ClientConnection : SocketConnection
//{
// public ClientConnection() : base( async (dd) =>
// {
// })
// {
// }
//}
}

View File

@ -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<byte[]> _commonMessagesChannel = Channel.CreateUnbounded<byte[]>();
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<byte[]> 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<Guid> Publish(OutgoingMessage message)
{
var bytes = MessageParser.PackMessage(message);
await _connection.SendAsync(bytes);
return MessageParser.GetMessageId(bytes);
}
public async ValueTask<IncommingMessage> 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();
}
}
}

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Sphagnum.Common\Sphagnum.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -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;
}
}
}

View File

@ -0,0 +1,17 @@
namespace Sphagnum.Common.UnitTests
{
public class ConnectionTests
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test1()
{
Assert.Pass();
}
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1 @@
global using NUnit.Framework;

View File

@ -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++;
}
}
}
}

View File

@ -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<byte[]> _queue = new();
public bool Connected => true;
public IConnection Accept()
{
return new TestConnection();
}
public Task<IConnection> AcceptAsync()
{
return Task.FromResult<IConnection>(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<int> ReceiveAsync(Memory<byte> 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<int> SendAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default)
{
_queue.Enqueue(buffer.Span.ToArray());
return ValueTask.FromResult(buffer.Length);
}
private async ValueTask<int> 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();
}
}
}

View File

@ -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<Func<byte[], Task>> messagesProcessorFactory)
{
return new SphagnumConnection(() => new TestConnection(), messagesProcessorFactory);
}
internal override Task<SphagnumConnection> CreateDefaultConnected(Func<Func<byte[], Task>> messagesProcessorFactory)
{
return Task.FromResult(new SphagnumConnection(() => new TestConnection(), messagesProcessorFactory));
}
}
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Sphagnum.Common\Sphagnum.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -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<byte>(data, 1);
// _ = await connection.SendAsync(data);
// var forResult = new List<byte>();
// 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<byte>(data, 1);
// _ = await connection.SendAsync(data);
// var forResult = new List<byte>();
// 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<byte>(data, 1);
// _ = await connection.SendAsync(data);
// var forResult = new List<byte>();
// 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<byte>(data, 1);
// _ = await connection.SendAsync(data);
// var forResult = new List<byte>();
// 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<byte>(data, 1);
// _ = await connection.SendAsync(data);
// var forResult = new List<byte>();
// 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);
// }
// }
//}

View File

@ -0,0 +1,7 @@
using System.Runtime.CompilerServices;
//Тесты
[assembly: InternalsVisibleTo("Sphagnum.Common.UnitTests")]
[assembly: InternalsVisibleTo("Sphagnum.Client")]
[assembly: InternalsVisibleTo("Sphagnum.Server")]

View File

@ -0,0 +1,14 @@
namespace Sphagnum.Common.Contracts.Administration.Enums
{
public enum ExchangeType : byte
{
/// <summary>
/// Раздает сообщения во все топики с подходящим ключём маршрутизации.
/// </summary>
Broadcast,
/// <summary>
/// Отправляет сообщение в одну из очередей с подходящим ключём маршрутизации.
/// </summary>
Topic,
}
}

View File

@ -0,0 +1,8 @@
namespace Sphagnum.Common.Contracts.Administration.Enums
{
public enum TopicType
{
Queue,
Stack,
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,8 @@
namespace Sphagnum.Common
{
internal static class Constants
{
public const int HashedUserDataSizeInfBytes = 32;
public const int PayloadRecieverBufferSize = 8192;
}
}

View File

@ -0,0 +1,8 @@
using Sphagnum.Common.Contracts.Messaging;
namespace Sphagnum.Common.Contracts
{
public interface ISphagnumClient : IMessagingClient
{
}
}

View File

@ -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<IConnection> AcceptAsync();
//todo прописать бросаемые исключения
void Bind(EndPoint endPoint);
ValueTask<int> ReceiveAsync(Memory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default);
//todo прописать бросаемые исключения
void Listen(int backlog);
//todo прописать бросаемые исключения
ValueTask<int> SendAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default);
//todo прописать бросаемые исключения
void Close();
bool Connected { get; }
}
}

View File

@ -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<SphagnumConnection> CreateDefaultConnected(Func<Func<byte[], Task>> 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<Func<byte[], Task>> messagesProcessorFactory)
{
return new SphagnumConnection(() => new SocketConnection(new Socket(SocketType.Stream, ProtocolType.Tcp)), messagesProcessorFactory);
}
}
}

View File

@ -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;
}
}

View File

@ -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,
}
}

View File

@ -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<IncommingMessage> Consume(CancellationToken cancellationToken);
public ValueTask<Guid> Publish(OutgoingMessage message);
public ValueTask Ack(Guid messageId);
public ValueTask Nack(Guid messageId);
public ValueTask Reject(Guid messageId);
}
}

View File

@ -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<byte> 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;
}
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace Sphagnum.Common.Contracts.Messaging.Messages
{
internal readonly ref struct AuthResultMessage
{
public readonly ReadOnlyMemory<byte> Payload;
}
}

View File

@ -0,0 +1,17 @@
using System;
namespace Sphagnum.Common.Contracts.Messaging.Messages
{
public readonly struct IncommingMessage
{
public readonly Guid MessageId;
public readonly ReadOnlyMemory<byte> Payload;
public IncommingMessage(Guid messageId, ReadOnlyMemory<byte> payload)
{
MessageId = messageId;
Payload = payload;
}
}
}

View File

@ -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<byte> Payload;
public OutgoingMessage(string exchange, RoutingKey routingKey, ReadOnlyMemory<byte> payload)
{
Exchange = exchange;
RoutingKey = routingKey;
Payload = payload;
}
public OutgoingMessage(string exchange, ReadOnlyMemory<byte> payload)
{
Exchange = exchange;
RoutingKey = new RoutingKey();
Payload = payload;
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,8 @@
using System;
namespace Sphagnum.Common.Exceptions
{
public class AuthException : Exception
{
}
}

View File

@ -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<Channel<byte[]>> channels = new ConcurrentQueue<Channel<byte[]>>();
public Channel<byte[]> Get()
{
if (channels.TryDequeue(out var channel))
{
return channel;
}
else
{
return Channel.CreateBounded<byte[]>(1);
}
}
public void Return(Channel<byte[]> channel)
{
if (channels.Count < _poolSize)
{
channels.Enqueue(channel);
}
}
}
}

View File

@ -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<IConnection> 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<int> ReceiveAsync(Memory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default)
{
return _socket.ReceiveAsync(buffer, socketFlags, cancellationToken);
}
public ValueTask<int> SendAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default)
{
return _socket.SendAsync(buffer, socketFlags, cancellationToken);
}
}
}

View File

@ -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<Guid, Channel<byte[]>> sendingItems = new ConcurrentDictionary<Guid, Channel<byte[]>>();
private readonly Func<Func<byte[], Task>> _messagesProcessorFactory;
private readonly Func<byte[], Task> _messagesProcessor;
public SphagnumConnection(Func<IConnection> connectionsFactory, Func<Func<byte[], Task>> messagesProcessorFactory)
{
_connection = connectionsFactory(); // new SocketConnection(new Socket(SocketType.Stream, ProtocolType.Tcp));
_messagesProcessorFactory = messagesProcessorFactory;
_messagesProcessor = _messagesProcessorFactory();
RecievingTask();
}
private SphagnumConnection(IConnection socket, Func<Func<byte[], Task>> 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<SphagnumConnection> 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<int> SendAsync(ReadOnlyMemory<byte> 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<byte[]> 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();
}
}
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
using System;
namespace Sphagnum.Common.Utils.Models
{
[Flags]
internal enum MessageFlags : ushort
{
None = 0,
HasRoutingKey = 1,
HasPayload = 2,
HasExchange = 4,
}
}

View File

@ -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,
}
}

View File

@ -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));
}
}
}

View File

@ -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
{
/// <summary>
/// Порядок передачи:
/// 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 байт
/// </summary>
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<byte> bytes)
{
var slice = bytes.Slice(7, 16);
return new Guid(slice);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Guid GetMessageId(ReadOnlyMemory<byte> bytes)
{
var slice = bytes.Slice(7, 16);
return new Guid(slice.Span);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void CopyId(ReadOnlySpan<byte> bytes, byte[] buffer)
{
var slice = bytes.Slice(7, 16);
slice.CopyTo(buffer.AsSpan(7, 16));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static MessageType GetMessageType(Span<byte> bytes)
{
return (MessageType)bytes[4];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static byte[] GetPayload(Span<byte> bytes)
{
var result = Array.Empty<byte>();
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<byte> 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<byte> bytes)
{
var value = BitConverter.ToUInt16(bytes.Slice(5, 2));
return (((MessageFlags)value & MessageFlags.HasRoutingKey) == MessageFlags.HasRoutingKey);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool HasPayload(Span<byte> bytes)
{
var value = BitConverter.ToUInt16(bytes.Slice(5, 2));
return (((MessageFlags)value & MessageFlags.HasPayload) == MessageFlags.HasPayload);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool HasExchange(Span<byte> bytes)
{
var value = BitConverter.ToUInt16(bytes.Slice(5, 2));
return (((MessageFlags)value & MessageFlags.HasExchange) == MessageFlags.HasExchange);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string GetExchangeName(Span<byte> 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<byte> 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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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"]

View File

@ -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<IMessagingClient, ClientDefault>();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();
app.Run();

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Sphagnum.Client\Sphagnum.Client.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -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"]

View File

@ -0,0 +1,12 @@
using Sphagnum.Common.Contracts.Login;
using Sphagnum.Server;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton<ConnectionFactory>();
builder.Services.AddHostedService<BrokerHost>();
var app = builder.Build();
app.MapControllers();
app.Run();

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..\src</DockerfileContext>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Sphagnum.Server\Sphagnum.Server.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1 @@
global using NUnit.Framework;

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Sphagnum.Client\Sphagnum.Client.csproj" />
<ProjectReference Include="..\Sphagnum.Server\Sphagnum.Server.csproj" />
</ItemGroup>
</Project>

View File

@ -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 }));
}
}
}

View File

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Sphagnum.Server.FuncTests")]

View File

@ -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();
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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<bool> 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;
}
}
}

View File

@ -0,0 +1,7 @@
namespace Sphagnum.Server.Cluster.Contracts
{
public interface IDistributor
{
ValueTask DistributeData(ReadOnlySpan<byte> data);
}
}

View File

@ -0,0 +1,12 @@
using Sphagnum.Server.Cluster.Contracts;
namespace Sphagnum.Server.Cluster.Services
{
internal class DistributorDefault : IDistributor
{
public ValueTask DistributeData(ReadOnlySpan<byte> data)
{
return ValueTask.CompletedTask;
}
}
}

View File

@ -0,0 +1,9 @@
namespace Sphagnum.Server.DataProcessing.Contracts
{
internal interface IDataProcessor
{
public bool RegisterCoprocessor(string key, Func<byte[], Task> func);
public Func<byte[], Task>? UnregisterCoprocessor(string key);
public ValueTask PutMessage(ReadOnlySpan<byte> message);
}
}

View File

@ -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<string, Func<byte[], Task>> _processors = new();
public ValueTask PutMessage(ReadOnlySpan<byte> message)
{
return ValueTask.CompletedTask;
}
public bool RegisterCoprocessor(string key, Func<byte[], Task> func)
{
return _processors.TryAdd(key, func);
}
public Func<byte[], Task>? UnregisterCoprocessor(string key)
{
if (_processors.TryRemove(key, out Func<byte[], Task>? res))
{
return res;
}
return null;
}
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Sphagnum.Common\Sphagnum.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,7 @@
namespace Sphagnum.Server.Storage.Messages.Contracts
{
internal interface IMessagesStorage
{
ValueTask LogMessage(ReadOnlyMemory<byte> message);
}
}

View File

@ -0,0 +1,12 @@
using Sphagnum.Server.Storage.Messages.Contracts;
namespace Sphagnum.Server.Storage.Messages.Services
{
internal class MessagesStorageDefault : IMessagesStorage
{
public ValueTask LogMessage(ReadOnlyMemory<byte> message)
{
return ValueTask.CompletedTask;
}
}
}

View File

@ -0,0 +1,13 @@
using Sphagnum.Common.Contracts.Login;
namespace Sphagnum.Server.Storage.Users.Contracts
{
internal interface IAuthInfoStorage
{
public ValueTask<bool> CheckRights(Span<byte> hashedUsername, Span<byte> hashedPassword, UserRights userRights, CancellationToken token = default);
public ValueTask AddUser(Span<byte> hashedUsername, Span<byte> hashedPassword, UserRights userRights);
public ValueTask SetRights(Span<byte> hashedUsername, UserRights userRights);
}
}

View File

@ -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<byte> RootUserLogin = new(HashCalculator.Calc("root"));
private readonly Vector<byte> RootUserPassword = new(HashCalculator.Calc("root"));
private readonly UserRights RootUserRights = UserRights.All;
public ValueTask AddUser(Span<byte> hashedUsername, Span<byte> hashedPassword, UserRights userRights)
{
return ValueTask.CompletedTask;
}
public ValueTask<bool> CheckRights(Span<byte> hashedUsername, Span<byte> hashedPassword, UserRights userRights, CancellationToken token = default)
{
var username = new Vector<byte>(hashedUsername);
var pwd = new Vector<byte>(hashedPassword);
return ValueTask.FromResult(username == RootUserLogin && pwd == RootUserPassword && (userRights & RootUserRights) == userRights);
}
public ValueTask SetRights(Span<byte> hashedUsername, UserRights userRights)
{
return ValueTask.CompletedTask;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Sphagnum.Server\Sphagnum.Server.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

89
src/Sphagnum.sln Normal file
View File

@ -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

19
src/docker-compose.dcproj Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectVersion>2.1</ProjectVersion>
<DockerTargetOS>Linux</DockerTargetOS>
<DockerPublishLocally>False</DockerPublishLocally>
<ProjectGuid>832db06c-c0f5-4608-b94f-86fde003c420</ProjectGuid>
<DockerLaunchAction>LaunchBrowser</DockerLaunchAction>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}/swagger</DockerServiceUrl>
<DockerServiceName>sphagnum.debugclient</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.yml" />
<None Include=".dockerignore" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,2 @@
version: '3.4'

22
src/docker-compose.yml Normal file
View File

@ -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

12
src/launchSettings.json Normal file
View File

@ -0,0 +1,12 @@
{
"profiles": {
"Docker Compose": {
"commandName": "DockerCompose",
"commandVersion": "1.0",
"serviceActions": {
"sphagnum.debugclient": "StartDebugging",
"sphagnum.debugservice": "StartDebugging"
}
}
}
}