-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4b99973
commit 40d79b7
Showing
7 changed files
with
361 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
// Copyright © 2021 GGG KILLER <[email protected]> | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software | ||
// and associated documentation files (the “Software”), to deal in the Software without | ||
// restriction, including without limitation the rights to use, copy, modify, merge, publish, | ||
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom | ||
// the Software is furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in all copies or | ||
// substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING | ||
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Tsu.BinaryParser.Parsers; | ||
|
||
namespace Tsu.BinaryParser.Builder | ||
{ | ||
/// <summary> | ||
/// A parser builder for complex objects. | ||
/// </summary> | ||
/// <typeparam name="T">The type of object being parsed.</typeparam> | ||
public abstract class AbstractParser<[DynamicallyAccessedMembers( | ||
DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | ||
| DynamicallyAccessedMemberTypes.PublicFields | ||
| DynamicallyAccessedMemberTypes.PublicProperties)] T> : IBinaryParser<T> | ||
{ | ||
private readonly List<ParserStep> _steps = new(); | ||
|
||
/// <summary> | ||
/// Initializes a new parser builder | ||
/// </summary> | ||
public AbstractParser() | ||
{ | ||
if (!typeof(T).GetConstructors().Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0)) | ||
throw new InvalidOperationException("Parsed types must have a public parameterless constructor."); | ||
} | ||
|
||
/// <summary> | ||
/// All steps currently registered in this parser. | ||
/// </summary> | ||
public IEnumerable<ParserStep> Steps => _steps; | ||
|
||
public abstract Option<int> MinimumByteCount { get; } | ||
public abstract Option<int> MaxmiumByteCount { get; } | ||
public abstract bool IsFixedSize { get; } | ||
|
||
/// <summary> | ||
/// Adds a new <see cref="MemberBindingStep" /> to the parser. | ||
/// </summary> | ||
/// <typeparam name="TMember">The type of the member being read/written to.</typeparam> | ||
/// <param name="expression"> | ||
/// The member to be used in this step. | ||
/// </param> | ||
/// <param name="parser"> | ||
/// The parser that is able to handle the type of this member. | ||
/// </param> | ||
/// <returns> | ||
/// The parser itself. | ||
/// </returns> | ||
/// <exception cref="ArgumentNullException"></exception> | ||
/// <exception cref="ArgumentException"></exception> | ||
protected AbstractParser<T> Add<TMember>(Expression<Func<T, TMember>> expression, IBinaryParser<T> parser) | ||
{ | ||
if (expression is null) | ||
throw new ArgumentNullException(nameof(expression)); | ||
if (expression.Body.NodeType != ExpressionType.MemberAccess) | ||
throw new ArgumentException("Provided expression is not a member access expression.", nameof(expression)); | ||
|
||
var memberExpression = (MemberExpression) expression.Body; | ||
if (memberExpression.Expression?.NodeType != ExpressionType.Parameter) | ||
throw new ArgumentException("Only direct members of the class can be used.", nameof(expression)); | ||
|
||
if (memberExpression.Member is FieldInfo fieldInfo) | ||
{ | ||
if (fieldInfo.IsInitOnly) | ||
throw new ArgumentException("The provided field cannot be written to.", nameof(expression)); | ||
_steps.Add(new MemberBindingStep(fieldInfo, parser)); | ||
} | ||
else if (memberExpression.Member is PropertyInfo propertyInfo) | ||
{ | ||
if (!propertyInfo.CanRead) | ||
throw new ArgumentException("The provided property cannot be read from."); | ||
if (!propertyInfo.CanWrite) | ||
throw new ArgumentException("The provided property cannot be written to."); | ||
_steps.Add(new MemberBindingStep(propertyInfo, parser)); | ||
} | ||
else | ||
{ | ||
throw new ArgumentException("Member being accessed is not a field nor property."); | ||
} | ||
|
||
return this; | ||
} | ||
|
||
public abstract long CalculateSize(T value); | ||
public abstract T Deserialize(IBinaryReader reader, IBinaryParsingContext context); | ||
public abstract ValueTask<T> DeserializeAsync(IBinaryReader reader, IBinaryParsingContext context, CancellationToken cancellationToken = default); | ||
public abstract void Serialize(Stream stream, IBinaryParsingContext context, T value); | ||
public abstract ValueTask SerializeAsync(Stream stream, IBinaryParsingContext context, T value, CancellationToken cancellationToken = default); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// Copyright © 2021 GGG KILLER <[email protected]> | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software | ||
// and associated documentation files (the “Software”), to deal in the Software without | ||
// restriction, including without limitation the rights to use, copy, modify, merge, publish, | ||
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom | ||
// the Software is furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in all copies or | ||
// substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING | ||
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
|
||
using System.Reflection; | ||
|
||
namespace Tsu.BinaryParser.Builder | ||
{ | ||
/// <summary> | ||
/// Represents a base parser step. | ||
/// </summary> | ||
public abstract class ParserStep | ||
{ | ||
private protected ParserStep() { } | ||
} | ||
|
||
/// <summary> | ||
/// Represents a parser step that binds the a parser to a field. | ||
/// </summary> | ||
public sealed class MemberBindingStep : ParserStep | ||
{ | ||
internal MemberBindingStep(MemberInfo member, object parser) | ||
{ | ||
InternalDebug.AssertNotNull(member); | ||
InternalDebug.AssertNotNull(parser); | ||
|
||
Member = member; | ||
Parser = parser; | ||
} | ||
|
||
/// <summary> | ||
/// The field's reflection info. | ||
/// </summary> | ||
public MemberInfo Member { get; } | ||
|
||
/// <summary> | ||
/// The parser to use for the field. | ||
/// </summary> | ||
public object Parser { get; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
176 changes: 176 additions & 0 deletions
176
Tsu.BinaryParser/test/Parsers/ChangeEndianessParserTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
|
||
using System.IO; | ||
using System.Threading.Tasks; | ||
using Tsu.BinaryParser.Parsers; | ||
|
||
namespace Tsu.BinaryParser.Tests.Parsers; | ||
|
||
public class ChangeEndianessParserTests | ||
{ | ||
[Fact] | ||
public void ChangeEndianessParser_HasZeroByteCount() | ||
{ | ||
// Given | ||
var parser = new ChangeEndianessParser(Endianess.LittleEndian); | ||
|
||
// When | ||
var min = parser.MinimumByteCount; | ||
var max = parser.MaxmiumByteCount; | ||
|
||
// Then | ||
Assert.True(min.IsSome); | ||
Assert.Equal(0, min.Value); | ||
Assert.True(max.IsSome); | ||
Assert.Equal(0, max.Value); | ||
} | ||
|
||
[Fact] | ||
public void ChangeEndianessParser_HasFixedSize() | ||
{ | ||
// Given | ||
var parser = new ChangeEndianessParser(Endianess.LittleEndian); | ||
|
||
// When | ||
var isFixedSize = parser.IsFixedSize; | ||
|
||
// Then | ||
Assert.True(isFixedSize); | ||
} | ||
|
||
[Fact] | ||
public void ChangeEndianessParser_CalculateSize_AlwaysReturnsZero() | ||
{ | ||
// Given | ||
var parser = new ChangeEndianessParser(Endianess.LittleEndian); | ||
|
||
// When | ||
var size = parser.CalculateSize(Unit.Value); | ||
|
||
// Then | ||
Assert.Equal(0, size); | ||
} | ||
|
||
[Fact] | ||
public void ChangeEndianessParser_Deserialize_ChangesContext() | ||
{ | ||
// Given | ||
using var stream = new MemoryStream(); | ||
using var reader = new BinaryReader(stream); | ||
var context = new BinaryParsingContext(Endianess.BigEndian); | ||
var parser = new ChangeEndianessParser(Endianess.LittleEndian); | ||
|
||
// When | ||
parser.Deserialize(reader, context); | ||
|
||
// Then | ||
Assert.Equal(Endianess.LittleEndian, context.Endianess); | ||
} | ||
|
||
[Fact] | ||
public void ChangeEndianessParser_Deserialize_DoesNotConsumeFromStream() | ||
{ | ||
// Given | ||
using var stream = TestData.RandomStream(128); | ||
using var reader = new BinaryReader(stream); | ||
var context = new BinaryParsingContext(Endianess.BigEndian); | ||
var parser = new ChangeEndianessParser(Endianess.LittleEndian); | ||
|
||
// When | ||
parser.Deserialize(reader, context); | ||
|
||
// Then | ||
Assert.Equal(0, stream.Position); | ||
} | ||
|
||
[Fact] | ||
public async Task ChangeEndianessParser_DeserializeAsync_ChangesContext() | ||
{ | ||
// Given | ||
using var stream = new MemoryStream(); | ||
using var reader = new BinaryReader(stream); | ||
var context = new BinaryParsingContext(Endianess.BigEndian); | ||
var parser = new ChangeEndianessParser(Endianess.LittleEndian); | ||
|
||
// When | ||
await parser.DeserializeAsync(reader, context); | ||
|
||
// Then | ||
Assert.Equal(Endianess.LittleEndian, context.Endianess); | ||
} | ||
|
||
[Fact] | ||
public async Task ChangeEndianessParser_DeserializeAsync_DoesNotConsumeFromStream() | ||
{ | ||
// Given | ||
using var stream = TestData.RandomStream(128); | ||
using var reader = new BinaryReader(stream); | ||
var context = new BinaryParsingContext(Endianess.BigEndian); | ||
var parser = new ChangeEndianessParser(Endianess.LittleEndian); | ||
|
||
// When | ||
await parser.DeserializeAsync(reader, context); | ||
|
||
// Then | ||
Assert.Equal(0, stream.Position); | ||
} | ||
|
||
[Fact] | ||
public void ChangeEndianessParser_Serialize_ChangesContext() | ||
{ | ||
// Given | ||
using var stream = new MemoryStream(); | ||
var context = new BinaryParsingContext(Endianess.BigEndian); | ||
var parser = new ChangeEndianessParser(Endianess.LittleEndian); | ||
|
||
// When | ||
parser.Serialize(stream, context, Unit.Value); | ||
|
||
// Then | ||
Assert.Equal(Endianess.LittleEndian, context.Endianess); | ||
} | ||
|
||
[Fact] | ||
public void ChangeEndianessParser_Serialize_DoesNotConsumeFromStream() | ||
{ | ||
// Given | ||
using var stream = new MemoryStream(); | ||
var context = new BinaryParsingContext(Endianess.BigEndian); | ||
var parser = new ChangeEndianessParser(Endianess.LittleEndian); | ||
|
||
// When | ||
parser.Serialize(stream, context, Unit.Value); | ||
|
||
// Then | ||
Assert.Equal(0, stream.Position); | ||
} | ||
|
||
[Fact] | ||
public async Task ChangeEndianessParser_SerializeAsync_ChangesContext() | ||
{ | ||
// Given | ||
using var stream = new MemoryStream(); | ||
var context = new BinaryParsingContext(Endianess.BigEndian); | ||
var parser = new ChangeEndianessParser(Endianess.LittleEndian); | ||
|
||
// When | ||
await parser.SerializeAsync(stream, context, Unit.Value); | ||
|
||
// Then | ||
Assert.Equal(Endianess.LittleEndian, context.Endianess); | ||
} | ||
|
||
[Fact] | ||
public async Task ChangeEndianessParser_SerializeAsync_DoesNotConsumeFromStream() | ||
{ | ||
// Given | ||
using var stream = TestData.RandomStream(128); | ||
var context = new BinaryParsingContext(Endianess.BigEndian); | ||
var parser = new ChangeEndianessParser(Endianess.LittleEndian); | ||
|
||
// When | ||
await parser.SerializeAsync(stream, context, Unit.Value); | ||
|
||
// Then | ||
Assert.Equal(0, stream.Position); | ||
} | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using System; | ||
using System.IO; | ||
|
||
namespace Tsu.BinaryParser.Tests; | ||
|
||
internal static class TestData | ||
{ | ||
public static MemoryStream RandomStream(int length) | ||
{ | ||
var buffer = new byte[length]; | ||
var random = new Random(0xA23B6FA); | ||
random.NextBytes(buffer); | ||
return new MemoryStream(buffer); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters