mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-07-30 17:30:40 +00:00
RateControl
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
using Esiur.Core;
|
||||
using Esiur.Protocol;
|
||||
|
||||
namespace Esiur.Tests.Unit.Integration;
|
||||
|
||||
[Collection("Integration")]
|
||||
public class AttachmentSecurityTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ConcurrentFetches_AreCoalescedIntoOneAttachRequest()
|
||||
{
|
||||
await using var cluster = await StartCluster().WaitAsync(TimeSpan.FromSeconds(10));
|
||||
|
||||
var fetches = Enumerable.Range(0, 16)
|
||||
.Select(_ => Task.Run(async () => await cluster.Connection.Get("sys/first")))
|
||||
.ToArray();
|
||||
var resources = await Task.WhenAll(fetches).WaitAsync(TimeSpan.FromSeconds(10));
|
||||
|
||||
Assert.All(resources, resource => Assert.Same(resources[0], resource));
|
||||
Assert.Equal(1, cluster.Connection.ResourceAttachRequestCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Connection_RefusesResourcesBeyondConfiguredAttachmentLimit()
|
||||
{
|
||||
await using var cluster = await StartCluster().WaitAsync(TimeSpan.FromSeconds(10));
|
||||
cluster.ClientWarehouse.Configuration.ResourceAttachments
|
||||
.MaximumAttachedResourcesPerConnection = 1;
|
||||
|
||||
Assert.NotNull(await cluster.Connection.Get("sys/first"));
|
||||
|
||||
var exception = await Assert.ThrowsAsync<AsyncException>(async () =>
|
||||
await cluster.Connection.Get("sys/second"));
|
||||
|
||||
Assert.Equal(ExceptionCode.AttachmentLimitExceeded, exception.Code);
|
||||
Assert.Equal(1, cluster.Connection.ResourceAttachRequestCount);
|
||||
}
|
||||
|
||||
static Task<IntegrationCluster> StartCluster()
|
||||
=> IntegrationCluster.StartAsync(async warehouse =>
|
||||
{
|
||||
await warehouse.Put("sys/first", new RateLimitedResource());
|
||||
await warehouse.Put("sys/second", new RateLimitedResource());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Esiur.Data;
|
||||
using Esiur.Net.Packets;
|
||||
using Esiur.Resource;
|
||||
|
||||
namespace Esiur.Tests.Unit;
|
||||
|
||||
public class ParserSecurityTests
|
||||
{
|
||||
[Fact]
|
||||
public void PacketParser_RejectsOversizedDeclarationBeforePayloadArrives()
|
||||
{
|
||||
var warehouse = new Warehouse();
|
||||
warehouse.Configuration.Parser.MaximumPacketSize = 1_024;
|
||||
var packet = new EpPacket(warehouse);
|
||||
|
||||
// EP notification with a RawData TDU whose four-byte length declares 1 MiB,
|
||||
// without supplying that payload. The declaration itself must be rejected.
|
||||
var data = new byte[] { 0x20, 0x60, 0x00, 0x10, 0x00, 0x00 };
|
||||
|
||||
Assert.Throws<ParserLimitException>(() =>
|
||||
{
|
||||
packet.Parse(data, 0, (uint)data.Length);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RawDataParser_EnforcesAllocationBudget()
|
||||
{
|
||||
var warehouse = new Warehouse();
|
||||
warehouse.Configuration.Parser.MaximumAllocationSize = 10;
|
||||
var data = Codec.Compose(new byte[20], warehouse, null);
|
||||
|
||||
Assert.Throws<ParserLimitException>(() => Codec.ParseSync(data, 0, warehouse));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StringParser_AccountsForDecodedUtf16Allocation()
|
||||
{
|
||||
var warehouse = new Warehouse();
|
||||
warehouse.Configuration.Parser.MaximumAllocationSize = 10;
|
||||
var data = Codec.Compose("123456", warehouse, null);
|
||||
|
||||
Assert.Throws<ParserLimitException>(() => Codec.ParseSync(data, 0, warehouse));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListParser_EnforcesCollectionItemBudget()
|
||||
{
|
||||
var warehouse = new Warehouse();
|
||||
var data = Codec.Compose(new object[] { true, false, true }, warehouse, null);
|
||||
warehouse.Configuration.Parser.MaximumCollectionItems = 2;
|
||||
|
||||
Assert.Throws<ParserLimitException>(() => Codec.ParseSync(data, 0, warehouse));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Net;
|
||||
using Esiur.Core;
|
||||
using Esiur.Data;
|
||||
using Esiur.Net;
|
||||
using Esiur.Net.Sockets;
|
||||
using Esiur.Protocol;
|
||||
using Esiur.Resource;
|
||||
|
||||
namespace Esiur.Tests.Unit;
|
||||
|
||||
public class PeerConnectionLimitTests
|
||||
{
|
||||
[Fact]
|
||||
public void Server_RejectsConnectionsAbovePerIpLimit_AndReleasesClosedSlot()
|
||||
{
|
||||
var warehouse = new Warehouse();
|
||||
warehouse.Configuration.Connections.MaximumConnectionsPerIpAddress = 1;
|
||||
|
||||
var server = new EpServer();
|
||||
server.Instance = new Instance(warehouse, 1, "server", server, null);
|
||||
server.Connections = new AutoList<EpConnection, NetworkServer<EpConnection>>(server);
|
||||
|
||||
var address = IPAddress.Parse("192.0.2.10");
|
||||
var firstSocket = new TestSocket(address, 10001);
|
||||
var first = new EpConnection();
|
||||
first.Assign(firstSocket);
|
||||
server.Add(first);
|
||||
|
||||
var rejectedSocket = new TestSocket(address, 10002);
|
||||
var rejected = new EpConnection();
|
||||
rejected.Assign(rejectedSocket);
|
||||
server.Add(rejected);
|
||||
|
||||
Assert.Single(server.Connections);
|
||||
Assert.Equal(SocketState.Closed, rejectedSocket.State);
|
||||
Assert.Equal(1, server.GetConnectionCount(address));
|
||||
|
||||
firstSocket.Close();
|
||||
Assert.Equal(0, server.GetConnectionCount(address));
|
||||
|
||||
var replacementSocket = new TestSocket(address, 10003);
|
||||
var replacement = new EpConnection();
|
||||
replacement.Assign(replacementSocket);
|
||||
server.Add(replacement);
|
||||
|
||||
Assert.Equal(SocketState.Established, replacementSocket.State);
|
||||
Assert.Equal(1, server.GetConnectionCount(address));
|
||||
|
||||
replacementSocket.Close();
|
||||
}
|
||||
|
||||
sealed class TestSocket : ISocket
|
||||
{
|
||||
public event DestroyedEvent? OnDestroy;
|
||||
|
||||
public SocketState State { get; private set; } = SocketState.Established;
|
||||
public INetworkReceiver<ISocket> Receiver { get; set; } = null!;
|
||||
public IPEndPoint RemoteEndPoint { get; }
|
||||
public IPEndPoint LocalEndPoint { get; } = new(IPAddress.Loopback, 10518);
|
||||
|
||||
public TestSocket(IPAddress address, int port)
|
||||
=> RemoteEndPoint = new IPEndPoint(address, port);
|
||||
|
||||
public AsyncReply<bool> SendAsync(byte[] message, int offset, int length)
|
||||
=> new(true);
|
||||
|
||||
public void Send(byte[] message) { }
|
||||
public void Send(byte[] message, int offset, int length) { }
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (State == SocketState.Closed)
|
||||
return;
|
||||
|
||||
State = SocketState.Closed;
|
||||
Receiver?.NetworkClose(this);
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Connect(string hostname, ushort port) => new(true);
|
||||
public bool Begin() => true;
|
||||
public AsyncReply<bool> BeginAsync() => new(true);
|
||||
public AsyncReply<ISocket> AcceptAsync() => new((ISocket)null!);
|
||||
public ISocket Accept() => null!;
|
||||
public void Hold() { }
|
||||
public void Unhold() { }
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
Close();
|
||||
OnDestroy?.Invoke(this);
|
||||
OnDestroy = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user