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