Project · I/O & HTTP
Build and verify a framed UTF-8 message
Define a versioned byte format with an explicit big-endian length and run twelve independent boundary checks.
All C# source files below are loaded together in the playground. Files written by a run live in its virtual directory.
Program.cs
FrameChecks.Run();
Protocol/FrameCodec.cs
using System.Buffers.Binary;
using System.Text;
public static class FrameCodec {
public const int MaxPayloadBytes = 32;
private static readonly Encoding TextEncoding = new UTF8Encoding(false, true);
public static byte[] Encode(string text) {
byte[] payload = TextEncoding.GetBytes(text);
if (payload.Length > MaxPayloadBytes) throw new InvalidDataException("Payload limit");
byte[] result = new byte[3 + payload.Length]; result[0] = 1;
BinaryPrimitives.WriteUInt16BigEndian(result.AsSpan(1, 2), checked((ushort)payload.Length));
payload.CopyTo(result, 3); return result;
}
// The caller owns input. This format accepts exactly one complete frame.
public static string Read(Stream input) {
Span<byte> header = stackalloc byte[3]; input.ReadExactly(header);
if (header[0] != 1) throw new InvalidDataException("Version");
int length = BinaryPrimitives.ReadUInt16BigEndian(header.Slice(1));
if (length > MaxPayloadBytes) throw new InvalidDataException("Payload limit");
byte[] payload = new byte[length]; input.ReadExactly(payload);
if (input.ReadByte() != -1) throw new InvalidDataException("Trailing bytes");
return TextEncoding.GetString(payload);
}
}
Testing/CappedReadStream.cs
public sealed class CappedReadStream : Stream {
private readonly MemoryStream inner;
public CappedReadStream(byte[] bytes) { inner = new MemoryStream(bytes, writable: false); }
public override bool CanRead => inner.CanRead;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => throw new NotSupportedException();
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override int Read(byte[] buffer, int offset, int count) {
ArgumentNullException.ThrowIfNull(buffer); ArgumentOutOfRangeException.ThrowIfNegative(offset); ArgumentOutOfRangeException.ThrowIfNegative(count);
if (offset > buffer.Length - count) throw new ArgumentException("Invalid buffer range");
return inner.Read(buffer, offset, Math.Min(count, 1));
}
public override int Read(Span<byte> buffer) => inner.Read(buffer.Slice(0, Math.Min(buffer.Length, 1)));
public override void Flush() { }
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
protected override void Dispose(bool disposing) { if (disposing) inner.Dispose(); base.Dispose(disposing); }
}
Testing/FrameChecks.cs
using System.Text;
public static class FrameChecks {
public static void Run() {
int checks = 0;
void Check(bool condition) { if (!condition) throw new Exception("Check failed"); checks++; }
void Reject<T>(byte[] data) where T : Exception {
using var input = new MemoryStream(data, writable: false);
try { FrameCodec.Read(input); } catch (T) { checks++; return; }
throw new Exception("Expected " + typeof(T).Name);
}
byte[] frame = FrameCodec.Encode("rune");
Check(Convert.ToHexString(frame) == "01000472756E65");
using (var input = new CappedReadStream(frame)) Check(FrameCodec.Read(input) == "rune" && input.CanRead);
using (var input = new MemoryStream(FrameCodec.Encode("λ"))) Check(FrameCodec.Read(input) == "λ");
Reject<InvalidDataException>(new byte[] { 2, 0, 0 });
Reject<InvalidDataException>(new byte[] { 1, 0, 33 });
Reject<EndOfStreamException>(new byte[] { 1, 0, 2, 65 });
Reject<DecoderFallbackException>(new byte[] { 1, 0, 2, 0xC3, 0x28 });
Reject<InvalidDataException>(new byte[] { 1, 0, 0, 99 });
using (var input = new CappedReadStream(frame)) {
void RejectRead(Action action) {
try { action(); } catch (ArgumentException) { checks++; return; }
throw new Exception("Expected invalid read rejection");
}
RejectRead(() => input.Read(null!, 0, 0));
RejectRead(() => input.Read(new byte[4], -1, 1));
RejectRead(() => input.Read(new byte[4], 0, -1));
RejectRead(() => input.Read(new byte[4], 0, 5));
}
Console.WriteLine("frame checks:" + checks);
Console.WriteLine("transport:memory only");
}
}
FramedMessage.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>