This commit is contained in:
parent
39ca103842
commit
42d572d857
3 changed files with 268 additions and 12 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
73
test/Nyanbyte.Sysnya.CoreTests/Binary/BinaryMapperTests.cs
Normal file
73
test/Nyanbyte.Sysnya.CoreTests/Binary/BinaryMapperTests.cs
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue