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;
///
/// 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.
///
internal class TestMultiSegmentProtocol : AyCodeBinaryHubProtocol
{
private const int SegmentSize = 256;
///
/// Serialize via Pipe (production-like stable memory blocks) instead of ArrayBufferWriter.
///
public new ReadOnlyMemory 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;
}
///
/// Split input into 256-byte segments before parsing — forces multi-segment ReadOnlySequence
/// through SequenceBinaryInput, exercising cross-boundary reads on every test.
///
public override bool TryParseMessage(ref ReadOnlySequence input, IInvocationBinder binder,
[NotNullWhen(true)] out HubMessage? message)
{
var multiSegment = CreateMultiSegmentSequence(input, SegmentSize);
return base.TryParseMessage(ref multiSegment, binder, out message);
}
private static ReadOnlySequence CreateMultiSegmentSequence(ReadOnlySequence 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(first, 0, current, current.Memory.Length);
}
private sealed class MemorySegment : ReadOnlySequenceSegment
{
public MemorySegment(ReadOnlyMemory memory) => Memory = memory;
public MemorySegment Append(ReadOnlyMemory memory)
{
var next = new MemorySegment(memory) { RunningIndex = RunningIndex + Memory.Length };
Next = next;
return next;
}
}
}