82 lines
3.1 KiB
C#
82 lines
3.1 KiB
C#
using System.Buffers;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO.Pipelines;
|
|
using AyCode.Services.SignalRs;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using Microsoft.AspNetCore.SignalR.Protocol;
|
|
|
|
namespace AyCode.Services.Server.Tests.SignalRs;
|
|
|
|
/// <summary>
|
|
/// Test protocol that simulates production Kestrel pipe behavior.
|
|
///
|
|
/// Write side: uses Pipe (not ArrayBufferWriter) so GetSpan/GetMemory return stable slab segments
|
|
/// — matching Kestrel's memory pool behavior. This ensures Span back-patching for length prefixes works.
|
|
///
|
|
/// Read side: splits the serialized bytes into 256-byte segments before parsing,
|
|
/// exercising SequenceBinaryInput cross-boundary reads at every boundary.
|
|
/// </summary>
|
|
internal class TestMultiSegmentProtocol : AyCodeBinaryHubProtocol
|
|
{
|
|
private const int SegmentSize = 256;
|
|
|
|
/// <summary>
|
|
/// Serialize via Pipe (production-like stable memory blocks) instead of ArrayBufferWriter.
|
|
/// </summary>
|
|
public new ReadOnlyMemory<byte> GetMessageBytes(HubMessage message)
|
|
{
|
|
var pipe = new Pipe();
|
|
WriteMessage(message, pipe.Writer);
|
|
pipe.Writer.Complete();
|
|
pipe.Reader.TryRead(out var result);
|
|
var bytes = result.Buffer.ToArray();
|
|
pipe.Reader.Complete();
|
|
return bytes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Split input into 256-byte segments before parsing — forces multi-segment ReadOnlySequence
|
|
/// through SequenceBinaryInput, exercising cross-boundary reads on every test.
|
|
/// </summary>
|
|
public override bool TryParseMessage(ref ReadOnlySequence<byte> input, IInvocationBinder binder,
|
|
[NotNullWhen(true)] out HubMessage? message)
|
|
{
|
|
var multiSegment = CreateMultiSegmentSequence(input, SegmentSize);
|
|
return base.TryParseMessage(ref multiSegment, binder, out message);
|
|
}
|
|
|
|
private static ReadOnlySequence<byte> CreateMultiSegmentSequence(ReadOnlySequence<byte> source, int chunkSize)
|
|
{
|
|
var bytes = source.ToArray();
|
|
|
|
// Each segment gets its own byte[] — matching Kestrel pool slab behavior
|
|
// where each pipe segment is a separate memory block.
|
|
var firstChunk = new byte[Math.Min(chunkSize, bytes.Length)];
|
|
Buffer.BlockCopy(bytes, 0, firstChunk, 0, firstChunk.Length);
|
|
var first = new MemorySegment(firstChunk);
|
|
var current = first;
|
|
|
|
for (var offset = chunkSize; offset < bytes.Length; offset += chunkSize)
|
|
{
|
|
var length = Math.Min(chunkSize, bytes.Length - offset);
|
|
var chunk = new byte[length];
|
|
Buffer.BlockCopy(bytes, offset, chunk, 0, length);
|
|
current = current.Append(chunk);
|
|
}
|
|
|
|
return new ReadOnlySequence<byte>(first, 0, current, current.Memory.Length);
|
|
}
|
|
|
|
private sealed class MemorySegment : ReadOnlySequenceSegment<byte>
|
|
{
|
|
public MemorySegment(ReadOnlyMemory<byte> memory) => Memory = memory;
|
|
|
|
public MemorySegment Append(ReadOnlyMemory<byte> memory)
|
|
{
|
|
var next = new MemorySegment(memory) { RunningIndex = RunningIndex + Memory.Length };
|
|
Next = next;
|
|
return next;
|
|
}
|
|
}
|
|
}
|