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