first commit
commit
443d4b2ea7
|
@ -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
|
|
@ -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/**
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace Sphagnum.Client
|
||||||
|
{
|
||||||
|
//internal class ClientConnection : SocketConnection
|
||||||
|
//{
|
||||||
|
// public ClientConnection() : base( async (dd) =>
|
||||||
|
// {
|
||||||
|
|
||||||
|
// })
|
||||||
|
// {
|
||||||
|
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
namespace Sphagnum.Common.UnitTests
|
||||||
|
{
|
||||||
|
public class ConnectionTests
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test1()
|
||||||
|
{
|
||||||
|
Assert.Pass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
global using NUnit.Framework;
|
|
@ -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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -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);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
//}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
//Тесты
|
||||||
|
[assembly: InternalsVisibleTo("Sphagnum.Common.UnitTests")]
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Sphagnum.Client")]
|
||||||
|
[assembly: InternalsVisibleTo("Sphagnum.Server")]
|
|
@ -0,0 +1,14 @@
|
||||||
|
namespace Sphagnum.Common.Contracts.Administration.Enums
|
||||||
|
{
|
||||||
|
public enum ExchangeType : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Раздает сообщения во все топики с подходящим ключём маршрутизации.
|
||||||
|
/// </summary>
|
||||||
|
Broadcast,
|
||||||
|
/// <summary>
|
||||||
|
/// Отправляет сообщение в одну из очередей с подходящим ключём маршрутизации.
|
||||||
|
/// </summary>
|
||||||
|
Topic,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Sphagnum.Common.Contracts.Administration.Enums
|
||||||
|
{
|
||||||
|
public enum TopicType
|
||||||
|
{
|
||||||
|
Queue,
|
||||||
|
Stack,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Sphagnum.Common
|
||||||
|
{
|
||||||
|
internal static class Constants
|
||||||
|
{
|
||||||
|
public const int HashedUserDataSizeInfBytes = 32;
|
||||||
|
public const int PayloadRecieverBufferSize = 8192;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
using Sphagnum.Common.Contracts.Messaging;
|
||||||
|
|
||||||
|
namespace Sphagnum.Common.Contracts
|
||||||
|
{
|
||||||
|
public interface ISphagnumClient : IMessagingClient
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Sphagnum.Common.Contracts.Messaging.Messages
|
||||||
|
{
|
||||||
|
internal readonly ref struct AuthResultMessage
|
||||||
|
{
|
||||||
|
public readonly ReadOnlyMemory<byte> Payload;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Sphagnum.Common.Exceptions
|
||||||
|
{
|
||||||
|
public class AuthException : Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Sphagnum.Common.Utils.Models
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
internal enum MessageFlags : ushort
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
HasRoutingKey = 1,
|
||||||
|
HasPayload = 2,
|
||||||
|
HasExchange = 4,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"]
|
|
@ -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();
|
|
@ -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>
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
|
@ -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"]
|
|
@ -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();
|
|
@ -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>
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
global using NUnit.Framework;
|
|
@ -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>
|
|
@ -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 }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Sphagnum.Server.FuncTests")]
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Sphagnum.Server.Cluster.Contracts
|
||||||
|
{
|
||||||
|
public interface IDistributor
|
||||||
|
{
|
||||||
|
ValueTask DistributeData(ReadOnlySpan<byte> data);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Sphagnum.Server.Storage.Messages.Contracts
|
||||||
|
{
|
||||||
|
internal interface IMessagesStorage
|
||||||
|
{
|
||||||
|
ValueTask LogMessage(ReadOnlyMemory<byte> message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
|
@ -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>
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
|
@ -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
|
|
@ -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>
|
|
@ -0,0 +1,2 @@
|
||||||
|
version: '3.4'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Docker Compose": {
|
||||||
|
"commandName": "DockerCompose",
|
||||||
|
"commandVersion": "1.0",
|
||||||
|
"serviceActions": {
|
||||||
|
"sphagnum.debugclient": "StartDebugging",
|
||||||
|
"sphagnum.debugservice": "StartDebugging"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue