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; } } }