// Generated from executable documentation examples. Edit the source region, then regenerate.
using System;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Buffers;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using CStructSharp;
using CStructSharp.Codecs;
using CStructSharp.Diagnostics;
using CStructSharp.Introspection;
using CStructSharp.Values;
internal static partial class Program
{
public static async Task Main()
{
await PipeReaderFraming();
Console.WriteLine("PASS pipe-reader");
}
/// Frames fixed-size and count-prefixed messages, retaining incomplete input across pipe reads.
private static async Task PipeReaderFraming()
{
// Four-byte frames arrive on a pipe in chunks that do not line up with the frame boundaries - exactly what a
// socket delivers. A PipeReader over a NetworkStream (PipeReader.Create(stream)) is used the same way.
var layout = new CStruct("struct frame { uint16 id; uint16 value; };");
int size = layout.GetStructSizeInBytes("frame");
var pipe = new Pipe();
// Supply chunks independently of the consumer so record boundaries need not match read boundaries.
Task producer = Task.Run(async () =>
{
byte[] frames = [1, 0, 10, 0, 2, 0, 20, 0, 3, 0, 30, 0, 4, 0, 40, 0, 5, 0, 50, 0];
foreach (int[] chunk in new[] { new[] { 0, 6 }, new[] { 6, 7 }, new[] { 13, 7 } })
{
await pipe.Writer.WriteAsync(frames.AsMemory(chunk[0], chunk[1]));
}
await pipe.Writer.CompleteAsync();
});
var ids = new List();
while (true)
{
var result = await pipe.Reader.ReadAsync();
ReadOnlySequence buffer = result.Buffer;
// Parse the whole frames the buffer holds - ParseMany reads a ReadOnlySequence directly, one frame per
// step - and hand the partial frame at the end back to the pipe: consumed up to the last whole frame,
// examined to the end, so the next ReadAsync waits for more bytes instead of returning the same ones.
long whole = buffer.Length / size * size;
foreach (StructValue frame in layout.ParseMany(buffer.Slice(0, whole), "frame"))
{
ids.Add(frame.Get("id"));
}
pipe.Reader.AdvanceTo(buffer.GetPosition(whole), buffer.End);
if (result.IsCompleted)
{
True(buffer.Length == whole, "the producer ended on a frame boundary");
break;
}
}
await producer;
await pipe.Reader.CompleteAsync();
Equal("1,2,3,4,5", string.Join(",", ids));
// A count-prefixed message has no fixed size: read the count first (a whole header, or wait), work out the
// message's length from it, and parse the message only when every byte of it is there.
var messages = new CStruct("struct message { uint8 count; uint8 payload[count]; };");
var chunks = new Pipe();
await chunks.Writer.WriteAsync(new byte[] { 2, 0xAA, 0xBB, 3, 0xCC });
await chunks.Writer.WriteAsync(new byte[] { 0xDD, 0xEE, 0 });
await chunks.Writer.CompleteAsync();
var lengths = new List();
while (true)
{
var result = await chunks.Reader.ReadAsync();
ReadOnlySequence buffer = result.Buffer;
while (messages.TryReadValue(buffer, "message.count", out byte count) && buffer.Length >= 1 + count)
{
StructValue message = messages.Parse(buffer.Slice(0, 1 + count), "message");
lengths.Add(message.Get("payload").Length);
buffer = buffer.Slice(1 + count);
}
chunks.Reader.AdvanceTo(buffer.Start, buffer.End);
if (result.IsCompleted)
{
break;
}
}
await chunks.Reader.CompleteAsync();
Equal("2,3,0", string.Join(",", lengths));
}
private static void Equal(T expected, T actual)
{
if (!EqualityComparer.Default.Equals(expected, actual))
{
throw new InvalidOperationException($"Expected '{expected}', received '{actual}'.");
}
}
private static void True(bool condition, string message)
{
if (!condition)
{
throw new InvalidOperationException(message);
}
}
}