Binary Mapper
Some checks failed
test / unit-test (push) Failing after 37s

This commit is contained in:
femsci 2023-12-01 13:48:10 +01:00
parent 39ca103842
commit 42d572d857
Signed by: femsci
GPG key ID: 08F7911F0E650C67
3 changed files with 268 additions and 12 deletions

View file

@ -1,4 +1,5 @@
using System.Buffers.Binary;
using System.Data;
namespace Nyanbyte.Sysnya.Binary;
@ -15,6 +16,10 @@ public class BinaryEnumerator
private readonly ReadOnlyMemory<byte> _fullData;
private int _position = 0;
public int Position => _position;
public int Remaining => _fullData.Length - _position;
public int Size => _fullData.Length;
public BinaryEnumerator Skip(int length)
{
_position += length;
@ -34,6 +39,19 @@ public class BinaryEnumerator
return this;
}
public uint ReadUInt32()
{
uint value = _bigEndian ? BinaryPrimitives.ReadUInt32BigEndian(_fullData.Span[_position..(_position + 4)]) : BinaryPrimitives.ReadUInt32LittleEndian(_fullData.Span[_position..(_position + 4)]);
_position += 4;
return value;
}
public BinaryEnumerator ReadUInt32(out uint value)
{
value = ReadUInt32();
return this;
}
public long ReadInt64()
{
long value = _bigEndian ? BinaryPrimitives.ReadInt64BigEndian(_fullData.Span[_position..(_position + 8)]) : BinaryPrimitives.ReadInt64LittleEndian(_fullData.Span[_position..(_position + 8)]);
@ -46,4 +64,87 @@ public class BinaryEnumerator
value = ReadInt64();
return this;
}
public ulong ReadUInt64()
{
ulong value = _bigEndian ? BinaryPrimitives.ReadUInt64BigEndian(_fullData.Span[_position..(_position + 8)]) : BinaryPrimitives.ReadUInt64LittleEndian(_fullData.Span[_position..(_position + 8)]);
_position += 8;
return value;
}
public BinaryEnumerator ReadUInt64(out ulong value)
{
value = ReadUInt64();
return this;
}
public short ReadInt16()
{
short value = _bigEndian ? BinaryPrimitives.ReadInt16BigEndian(_fullData.Span[_position..(_position + 2)]) : BinaryPrimitives.ReadInt16LittleEndian(_fullData.Span[_position..(_position + 2)]);
_position += 2;
return value;
}
public BinaryEnumerator ReadInt16(out short value)
{
value = ReadInt16();
return this;
}
public ushort ReadUInt16()
{
ushort value = _bigEndian ? BinaryPrimitives.ReadUInt16BigEndian(_fullData.Span[_position..(_position + 2)]) : BinaryPrimitives.ReadUInt16LittleEndian(_fullData.Span[_position..(_position + 2)]);
_position += 2;
return value;
}
public BinaryEnumerator ReadUInt16(out ushort value)
{
value = ReadUInt16();
return this;
}
public float ReadF32()
{
float value = _bigEndian ? BinaryPrimitives.ReadSingleBigEndian(_fullData.Span.Slice(_position)) : BinaryPrimitives.ReadSingleLittleEndian(_fullData.Span.Slice(_position));
_position += 4;
return value;
}
public BinaryEnumerator ReadF32(out float value)
{
value = ReadF32();
return this;
}
public double ReadF64()
{
double value = _bigEndian ? BinaryPrimitives.ReadDoubleBigEndian(_fullData.Span.Slice(_position)) : BinaryPrimitives.ReadDoubleLittleEndian(_fullData.Span.Slice(_position));
_position += 8;
return value;
}
public BinaryEnumerator ReadF64(out double value)
{
value = ReadF64();
return this;
}
public TNumeric ReadNumeric<TNumeric>() where TNumeric : struct, IConvertible
{
//TODO Refactor
object val = typeof(TNumeric).Name switch
{
"Int16" => ReadInt16(),
"UInt16" => ReadUInt16(),
"Int32" => ReadInt32(),
"UInt32" => ReadUInt32(),
"Int64" => ReadInt64(),
"UInt64" => ReadUInt64(),
_ => throw new DataException($"Invalid type: {typeof(TNumeric).FullName}")
};
return (TNumeric)val;
}
}

View file

@ -3,23 +3,105 @@ using System.Linq.Expressions;
namespace Nyanbyte.Sysnya.Binary;
public class BinaryStructMapperBuilder<T>
public class BinaryStructMapperBuilder<T> where T : notnull, new()
{
public BinaryStructMapperBuilder(T obj)
public BinaryStructMapperBuilder(int minMemoryLen = 0, bool bigEndian = true)
{
ArgumentNullException.ThrowIfNull(obj);
_obj = obj;
_minMemoryLen = minMemoryLen;
_bigEndian = bigEndian;
}
private readonly int _minMemoryLen;
private readonly bool _bigEndian;
private readonly LinkedList<Action<T, BinaryEnumerator>> _funcPipeline = new();
[NotNull]
private readonly T _obj;
private readonly Stack<Action<Memory<byte>>> _funcStack = new();
public BinaryStructMapperBuilder<T> BindU32<M>(Expression<Func<M, T>> property)
public BinaryStructMapperBuilder<T> BindI16<M>(Expression<Func<T, M>> property)
{
var member = property.Body as MemberExpression;
ArgumentNullException.ThrowIfNull(member);
_funcStack.Push(m => _obj.GetType().GetProperty(member.Member.Name)!.SetValue(_obj, m));
Bind<M, short>(property);
return this;
}
public BinaryStructMapperBuilder<T> BindU16<M>(Expression<Func<T, M>> property)
{
Bind<M, ushort>(property);
return this;
}
public BinaryStructMapperBuilder<T> BindI32<M>(Expression<Func<T, M>> property)
{
Bind<M, int>(property);
return this;
}
public BinaryStructMapperBuilder<T> BindU32<M>(Expression<Func<T, M>> property)
{
Bind<M, uint>(property);
return this;
}
public BinaryStructMapperBuilder<T> BindI64<M>(Expression<Func<T, M>> property)
{
Bind<M, long>(property);
return this;
}
public BinaryStructMapperBuilder<T> BindU64<M>(Expression<Func<T, M>> property)
{
Bind<M, ulong>(property);
return this;
}
private void Bind<M, TNumeric>(Expression<Func<T, M>> p) where TNumeric : struct, IConvertible
{
var member = p.Body as MemberExpression;
ArgumentNullException.ThrowIfNull(member);
var prop = typeof(T).GetProperty(member.Member.Name)!;
_funcPipeline.AddLast((obj, enu) =>
{
TNumeric value = enu.ReadNumeric<TNumeric>();
prop.SetValue(obj, value);
});
}
public BinaryStructMapper<T> Build()
{
return new BinaryStructMapper<T>(_funcPipeline, _minMemoryLen, _bigEndian);
}
}
public class BinaryStructMapper<T> where T : notnull, new()
{
internal BinaryStructMapper(LinkedList<Action<T, BinaryEnumerator>> funcPipeline, int minMemoryLen, bool bigEndian)
{
_funcPipeline = funcPipeline;
_minMemoryLen = minMemoryLen;
_bigEndian = bigEndian;
}
private readonly int _minMemoryLen;
private readonly bool _bigEndian;
private readonly LinkedList<Action<T, BinaryEnumerator>> _funcPipeline;
public T Map(byte[] bytes)
{
return Map(bytes.AsMemory());
}
public T Map(Memory<byte> bytes)
{
BinaryEnumerator enu = new(bytes, _bigEndian);
if (bytes.Length < _minMemoryLen)
{
throw new ArgumentException($"Data shorter than minimum expected: expected {_minMemoryLen} bytes; got: {bytes.Length} bytes");
}
T obj = Activator.CreateInstance<T>();
var enumerator = _funcPipeline.GetEnumerator();
while (enumerator.MoveNext())
{
var func = enumerator.Current;
func.Invoke(obj, enu);
}
return obj;
}
}

View file

@ -0,0 +1,73 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Threading.Tasks;
using Nyanbyte.Sysnya.Binary;
namespace Nyanbyte.Sysnya.CoreTests.Binary;
public class BinaryMapperTests
{
private class Dummy
{
public short Int16 { get; set; }
public ushort UInt16 { get; set; }
public int Int32 { get; set; }
public uint UInt32 { get; set; }
public long Int64 { get; set; }
public ulong UInt64 { get; set; }
}
[Fact]
public void Map32Bit()
{
// Given
Span<byte> bytes = stackalloc byte[8];
BinaryPrimitives.WriteUInt32BigEndian(bytes, 0xfeedbeef);
BinaryPrimitives.WriteInt32BigEndian(bytes[4..], 0xbeef);
BinaryStructMapperBuilder<Dummy> b = new(0, true);
// When
var mapper = b.BindU32(p => p.UInt32).BindI32(p => p.Int32).Build();
var dummy = mapper.Map(bytes.ToArray());
// Then
Assert.Equal(0xfeedbeef, dummy.UInt32);
Assert.Equal(0xbeef, dummy.Int32);
}
[Fact]
public void MapAll()
{
// Given
Span<byte> bytes = stackalloc byte[28];
BinaryPrimitives.WriteUInt16BigEndian(bytes[0..], 0xbabe);
BinaryPrimitives.WriteInt16BigEndian(bytes[2..], 0xbf);
BinaryPrimitives.WriteUInt32BigEndian(bytes[4..], 0xfeedbeef);
BinaryPrimitives.WriteInt32BigEndian(bytes[8..], 0xbeef);
BinaryPrimitives.WriteUInt64BigEndian(bytes[12..], 0xbad1dea0babebebe);
BinaryPrimitives.WriteInt64BigEndian(bytes[20..], 0x1dea0babebebe);
BinaryStructMapperBuilder<Dummy> b = new(0, true);
// When
var mapper = b
.BindU16(p => p.UInt16).BindI16(p => p.Int16)
.BindU32(p => p.UInt32).BindI32(p => p.Int32)
.BindU64(p => p.UInt64).BindI64(p => p.Int64)
.Build();
var dummy = mapper.Map(bytes.ToArray());
// Then
Assert.Equal(0xbabe, dummy.UInt16);
Assert.Equal(0xbf, dummy.Int16);
Assert.Equal(0xfeedbeef, dummy.UInt32);
Assert.Equal(0xbeef, dummy.Int32);
Assert.Equal(0xbad1dea0babebebe, dummy.UInt64);
Assert.Equal(0x1dea0babebebe, dummy.Int64);
}
}