Skip to content

Instantly share code, notes, and snippets.

@timyhac
Last active July 8, 2024 10:39
Show Gist options
  • Save timyhac/085963dcd6217528f0b4e99bd1f56fb7 to your computer and use it in GitHub Desktop.
Save timyhac/085963dcd6217528f0b4e99bd1f56fb7 to your computer and use it in GitHub Desktop.
A helper class for parsing binary structures that are Big-Endian encoded (as is common for network protocols).
using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace BinaryStruct
{
public ref struct Reader
{
public enum Endian
{
Little,
Big
}
private readonly ReadOnlySpan<byte> _buffer;
public Reader(ReadOnlySpan<byte> buffer)
{
_buffer = buffer;
Cursor = 0;
}
public int Cursor { get; set; }
public int Length => _buffer.Length;
public Endian Endianess { get; set; } = Endian.Big;
public static implicit operator Reader(byte[] buffer) => new Reader(buffer);
public void Skip(int n)
{
Cursor += n;
}
public bool Take(int n, out ReadOnlySpan<byte> value, int? cursor = default)
{
value = default;
Cursor = (cursor ?? Cursor) + n;
if (Cursor > Length)
return false;
value = _buffer.Slice(Cursor - n, n);
return true;
}
public bool U8(out byte value, int? cursor = default)
{
value = default;
Cursor = (cursor ?? Cursor) + 1;
if (Cursor > Length)
return false;
value = _buffer[Cursor - 1];
return true;
}
public bool U16(out ushort value, int? cursor = default, Endian? endianness = default)
{
value = default;
Cursor = (cursor ?? Cursor) + 2;
if (Cursor > Length)
return false;
var slice = _buffer.Slice(Cursor - 2, 2);
value = (endianness ?? Endianess) switch
{
Endian.Little => ReadUInt16LittleEndian(slice),
Endian.Big => ReadUInt16BigEndian(slice),
};
return true;
}
public bool U32(out uint value, int? cursor = default, Endian? endianness = default)
{
value = default;
Cursor = (cursor ?? Cursor) + 4;
if (Cursor > Length)
return false;
var slice = _buffer.Slice(Cursor - 4, 4);
value = (endianness ?? Endianess) switch
{
Endian.Little => ReadUInt32LittleEndian(slice),
Endian.Big => ReadUInt32BigEndian(slice),
};
return true;
}
}
}
namespace BinaryStruct.Tests
{
public class ReaderTests
{
[Fact]
public void Test1()
{
var reader = new Reader([0x00, 0x01, 0x02, 0x03]);
reader.U8(out var zero);
reader.U8(out var one);
reader.U8(out var two);
reader.U8(out var three);
Assert.Equal(0, zero);
Assert.Equal(1, one);
Assert.Equal(2, two);
Assert.Equal(3, three);
}
[Fact]
public void Test2()
{
var reader = new Reader([0x00, 0x01, 0x02, 0x03]);
reader.U16(out var x0001);
reader.U16(out var x0203);
Assert.Equal(0x00_01, x0001);
Assert.Equal(0x02_03, x0203);
}
[Fact]
public void Test3()
{
var reader = new Reader([0x00, 0x01, 0x02, 0x03]);
reader.U32(out var x00010203);
Assert.Equal((uint)0x00_01_02_03, x00010203);
}
[Fact]
public void Test4()
{
var reader = new Reader([0x00, 0x01, 0x02, 0x03]);
reader.Skip(1);
reader.Take(2, out var slice);
byte[] expected = [0x01, 0x02];
Assert.Equal(expected, slice.ToArray());
}
[Fact]
public void Test5()
{
var reader = new Reader([0x00, 0x01, 0x02, 0x03]);
reader.Cursor += 4;
var success = reader.U8(out _);
Assert.False(success);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment