diff --git a/Kaitai.Struct.Runtime.Async.Tests/CancelableTestsBase.cs b/Kaitai.Struct.Runtime.Async.Tests/CancelableTestsBase.cs new file mode 100644 index 0000000..8904346 --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/CancelableTestsBase.cs @@ -0,0 +1,54 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Kaitai.Struct.Runtime.Async.Tests +{ + public abstract class CancelableTestsBase + { + protected readonly CancellationToken CancellationToken; + + protected CancelableTestsBase(bool isTestingCancellation) + { + CancellationToken = new CancellationToken(isTestingCancellation); + } + + protected async Task Evaluate(Func assertFunc) + { + if (CancellationToken.IsCancellationRequested) + { + await Assert.ThrowsAsync(assertFunc); + } + else + { + await assertFunc(); + } + } + + protected async Task EvaluateMaybeCancelled(Func assertFunc) + { + try + { + await assertFunc(); + } + catch (TaskCanceledException) + { + } + } + + protected async Task Evaluate(Func assertFunc) where TExpectedException : Exception + { + try + { + await assertFunc(); + } + catch (TaskCanceledException) + { + } + catch (TExpectedException) + { + } + } + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async.Tests/Data/BitsData.cs b/Kaitai.Struct.Runtime.Async.Tests/Data/BitsData.cs new file mode 100644 index 0000000..2f26fde --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/Data/BitsData.cs @@ -0,0 +1,166 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Kaitai.Struct.Runtime.Async.Tests +{ + public class BitsData + { + public static IEnumerable BitsBeData => + new List<(ulong expected, byte[] streamContent, int bitsCount)> + { + (0b_0000_0000, new byte[] {0x00}, 0), + (0b_0000_0000, new byte[] {0x00}, 1), + (0b_0000_0001, new byte[] {0b_1000_0000}, 1), + (0b_0000_0001, new byte[] {0b_1100_0000}, 1), + (0b_0000_0011, new byte[] {0b_1100_0000}, 2), + (0b_0000_0111, new byte[] {0b_1110_0000}, 3), + (0b_0000_1111, new byte[] {0b_1111_0000}, 4), + (0b_0001_1111, new byte[] {0b_1111_1000}, 5), + (0b_0011_1111, new byte[] {0b_1111_1100}, 6), + (0b_0111_1111, new byte[] {0b_1111_1110}, 7), + (0b_1111_1111, new byte[] {0b_1111_1111}, 8), + + (0b_0000_0001_1111_1111, new byte[] {0b_1111_1111, 0b_1000_0000}, 9), + (0b_0000_0011_1111_1111, new byte[] {0b_1111_1111, 0b_1100_0000}, 10), + (0b_0000_0111_1111_1111, new byte[] {0b_1111_1111, 0b_1110_0000}, 11), + (0b_0000_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_0000}, 12), + (0b_0001_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1000}, 13), + (0b_0011_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1100}, 14), + (0b_0111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1110}, 15), + (0b_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111}, 16), + + (0b_0000_0001_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1000_0000}, 17), + (0b_0000_0011_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1100_0000}, 18), + (0b_0000_0111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1110_0000}, 19), + (0b_0000_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_0000}, 20), + (0b_0001_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1000}, 21), + (0b_0011_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1100}, 22), + (0b_0111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1110}, 23), + (0b_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111}, 24), + + (0b_0000_0001_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1000_0000}, 25), + (0b_0000_0011_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1100_0000}, 26), + (0b_0000_0111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1110_0000}, 27), + (0b_0000_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_0000}, 28), + (0b_0001_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1000}, 29), + (0b_0011_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1100}, 30), + (0b_0111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1110}, 31), + (0b_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111}, 32), + + (0b_0000_0001_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1000_0000}, 33), + (0b_0000_0011_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1100_0000}, 34), + (0b_0000_0111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1110_0000}, 35), + (0b_0000_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_0000}, 36), + (0b_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1000}, 37), + (0b_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1100}, 38), + (0b_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1110}, 39), + (0b_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111}, 40), + + (0b_0000_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1000_0000}, 41), + (0b_0000_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1100_0000}, 42), + (0b_0000_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1110_0000}, 43), + (0b_0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_0000}, 44), + (0b_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1000}, 45), + (0b_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1100}, 46), + (0b_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1110}, 47), + (0b_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111}, 48), + + (0b_0000_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1000_0000}, 49), + (0b_0000_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1100_0000}, 50), + (0b_0000_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1110_0000}, 51), + (0b_0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_0000}, 52), + (0b_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1000}, 53), + (0b_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1100}, 54), + (0b_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1110}, 55), + (0b_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111}, 56), + + (0b_0000_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1000_0000}, 57), + (0b_0000_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1100_0000}, 58), + (0b_0000_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1110_0000}, 59), + (0b_0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_0000}, 60), + (0b_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1000}, 61), + (0b_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1100}, 62), + (0b_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1110}, 63), + (0b_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111}, 64), + }.Select(t => new object[] { t.expected, t.streamContent, t.bitsCount }); + + public static IEnumerable BitsLeData => + new List<(ulong expected, byte[] streamContent, int bitsCount)> + { + (0b_0000_0000, new byte[] {0x00}, 0), + (0b_0000_0000, new byte[] {0x00}, 1), + (0b_0000_0001, new byte[] {0b_0000_0001}, 1), + (0b_0000_0001, new byte[] {0b_0000_0011}, 1), + (0b_0000_0011, new byte[] {0b_0000_0011}, 2), + (0b_0000_0111, new byte[] {0b_0000_0111}, 3), + (0b_0000_1111, new byte[] {0b_0000_1111}, 4), + (0b_0001_1111, new byte[] {0b_0001_1111}, 5), + (0b_0011_1111, new byte[] {0b_0011_1111}, 6), + (0b_0111_1111, new byte[] {0b_0111_1111}, 7), + (0b_1111_1111, new byte[] {0b_1111_1111}, 8), + + (0b_0000_0001_1111_1111, new byte[] {0b_1111_1111, 0b_0000_0001}, 9), + (0b_0000_0011_1111_1111, new byte[] {0b_1111_1111, 0b_0000_0011}, 10), + (0b_0000_0111_1111_1111, new byte[] {0b_1111_1111, 0b_0000_0111}, 11), + (0b_0000_1111_1111_1111, new byte[] {0b_1111_1111, 0b_0000_1111}, 12), + (0b_0001_1111_1111_1111, new byte[] {0b_1111_1111, 0b_0001_1111}, 13), + (0b_0011_1111_1111_1111, new byte[] {0b_1111_1111, 0b_0011_1111}, 14), + (0b_0111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_0111_1111}, 15), + (0b_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111}, 16), + + (0b_0000_0001_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_0000_0001}, 17), + (0b_0000_0011_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_0000_0011}, 18), + (0b_0000_0111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_0000_0111}, 19), + (0b_0000_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_0000_1111}, 20), + (0b_0001_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_0001_1111}, 21), + (0b_0011_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_0011_1111}, 22), + (0b_0111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_0111_1111}, 23), + (0b_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111}, 24), + + (0b_0000_0001_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0001}, 25), + (0b_0000_0011_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0011}, 26), + (0b_0000_0111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0111}, 27), + (0b_0000_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_1111}, 28), + (0b_0001_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0001_1111}, 29), + (0b_0011_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0011_1111}, 30), + (0b_0111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0111_1111}, 31), + (0b_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111}, 32), + + (0b_0000_0001_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0001}, 33), + (0b_0000_0011_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0011}, 34), + (0b_0000_0111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0111}, 35), + (0b_0000_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_1111}, 36), + (0b_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0001_1111}, 37), + (0b_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0011_1111}, 38), + (0b_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0111_1111}, 39), + (0b_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111}, 40), + + (0b_0000_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0001}, 41), + (0b_0000_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0011}, 42), + (0b_0000_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0111}, 43), + (0b_0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_1111}, 44), + (0b_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0001_1111}, 45), + (0b_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0011_1111}, 46), + (0b_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0111_1111}, 47), + (0b_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111}, 48), + + (0b_0000_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0001}, 49), + (0b_0000_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0011}, 50), + (0b_0000_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0111}, 51), + (0b_0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_1111}, 52), + (0b_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0001_1111}, 53), + (0b_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0011_1111}, 54), + (0b_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0111_1111}, 55), + (0b_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111}, 56), + + (0b_0000_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0001}, 57), + (0b_0000_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0011}, 58), + (0b_0000_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_0111}, 59), + (0b_0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0000_1111}, 60), + (0b_0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0001_1111}, 61), + (0b_0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0011_1111}, 62), + (0b_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_0111_1111}, 63), + (0b_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, new byte[] {0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111, 0b_1111_1111}, 64), + }.Select(t => new object[] { t.expected, t.streamContent, t.bitsCount }); + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async.Tests/Data/DecimalData.cs b/Kaitai.Struct.Runtime.Async.Tests/Data/DecimalData.cs new file mode 100644 index 0000000..4cec9a0 --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/Data/DecimalData.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Kaitai.Struct.Runtime.Async.Tests +{ + public class DecimalData + { + public static IEnumerable Decimal4Data => + new List<(float expected, byte[] streamContent)> + { + (0f, BitConverter.GetBytes(0f)), + (1f, BitConverter.GetBytes(1f)), + (0.1f, BitConverter.GetBytes(0.1f)), + (1.1f, BitConverter.GetBytes(1.1f)), + (float.MinValue, BitConverter.GetBytes(float.MinValue)), + (float.MaxValue, BitConverter.GetBytes(float.MaxValue)) + }.Select(t => new object[] {t.expected, t.streamContent}); + + public static IEnumerable Decimal8Data => + new List<(double expected, byte[] streamContent)> + { + (0d, BitConverter.GetBytes(0d)), + (1d, BitConverter.GetBytes(1d)), + (0.1d, BitConverter.GetBytes(0.1d)), + (1.1d, BitConverter.GetBytes(1.1d)), + (double.MinValue, BitConverter.GetBytes(double.MinValue)), + (double.MaxValue, BitConverter.GetBytes(double.MaxValue)) + }.Select(t => new object[] {t.expected, t.streamContent}); + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async.Tests/Data/IntegralData.cs b/Kaitai.Struct.Runtime.Async.Tests/Data/IntegralData.cs new file mode 100644 index 0000000..ff5c52f --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/Data/IntegralData.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Kaitai.Struct.Runtime.Async.Tests +{ + public class IntegralData + { + public static IEnumerable Integral1Data => + new List<(sbyte expected, byte[] streamContent)> + { + (0x00, new byte[] {0x00}), + (0x01, new byte[] {0x01}), + (0x7F, new byte[] {0x7F}), + (unchecked((sbyte) 0xFF), new byte[] {0xFF}) + }.Select(t => new object[] {t.expected, t.streamContent}); + + public static IEnumerable Integral2Data => + new List<(short expected, byte[] streamContent)> + { + (0x00, new byte[] {0x00, 0x00}), + (0x01, new byte[] {0x00, 0x01}), + (0xFF, new byte[] {0x00, 0xFF}), + (0x01_FF, new byte[] {0x01, 0xFF}), + (0x7F_FF, new byte[] {0x7F, 0xFF}), + (unchecked((short) 0xFF_FF), new byte[] {0xFF, 0xFF}) + }.Select(t => new object[] {t.expected, t.streamContent}); + + public static IEnumerable Integral4Data => + new List<(int expected, byte[] streamContent)> + { + (0x00, new byte[] {0x00, 0x00, 0x00, 0x00}), + (0x01, new byte[] {0x00, 0x00, 0x00, 0x01}), + (0xFF, new byte[] {0x00, 0x00, 0x00, 0xFF}), + (0x01_FF, new byte[] {0x00, 0x00, 0x01, 0xFF}), + (0x7F_FF, new byte[] {0x00, 0x00, 0x7F, 0xFF}), + (0xFF_FF, new byte[] {0x00, 0x00, 0xFF, 0xFF}), + (0x01_FF_FF, new byte[] {0x00, 0x01, 0xFF, 0xFF}), + (0x7F_FF_FF, new byte[] {0x00, 0x7F, 0xFF, 0xFF}), + (0xFF_FF_FF, new byte[] {0x00, 0xFF, 0xFF, 0xFF}), + (0x01_FF_FF_FF, new byte[] {0x01, 0xFF, 0xFF, 0xFF}), + (0x7F_FF_FF_FF, new byte[] {0x7F, 0xFF, 0xFF, 0xFF}), + (unchecked((int) 0xFF_FF_FF_FF), new byte[] {0xFF, 0xFF, 0xFF, 0xFF}) + }.Select(t => new object[] {t.expected, t.streamContent}); + + public static IEnumerable Integral8Data => + new List<(long expected, byte[] streamContent)> + { + (0, new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), + (1, new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}), + (0xFF, new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}), + + (0x01_FF, new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF}), + (0x7F_FF, new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF}), + (0xFF_FF, new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}), + + (0x01_FF_FF, new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF}), + (0x7F_FF_FF, new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF}), + (0xFF_FF_FF, new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF}), + + (0x01_FF_FF_FF, new byte[] {0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF}), + (0x7F_FF_FF_FF, new byte[] {0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF}), + (0xFF_FF_FF_FF, new byte[] {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}), + + (0x01_FF_FF_FF_FF, new byte[] {0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF}), + (0x7F_FF_FF_FF_FF, new byte[] {0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF}), + (0xFF_FF_FF_FF_FF, new byte[] {0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}), + + (0x01_FF_FF_FF_FF_FF, new byte[] {0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}), + (0x7F_FF_FF_FF_FF_FF, new byte[] {0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}), + (0xFF_FF_FF_FF_FF_FF, new byte[] {0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}), + + (0x01_FF_FF_FF_FF_FF_FF, new byte[] {0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}), + (0x7F_FF_FF_FF_FF_FF_FF, new byte[] {0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}), + (0xFF_FF_FF_FF_FF_FF_FF, new byte[] {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}), + + (0x01_FF_FF_FF_FF_FF_FF_FF, new byte[] {0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}), + (0x7F_FF_FF_FF_FF_FF_FF_FF, new byte[] {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}), + (unchecked((long) 0xFF_FF_FF_FF_FF_FF_FF_FF), + new byte[] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + }.Select(t => new object[] {t.expected, t.streamContent}); + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async.Tests/Kaitai.Struct.Runtime.Async.Tests.csproj b/Kaitai.Struct.Runtime.Async.Tests/Kaitai.Struct.Runtime.Async.Tests.csproj new file mode 100644 index 0000000..847abad --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/Kaitai.Struct.Runtime.Async.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + diff --git a/Kaitai.Struct.Runtime.Async.Tests/KaitaiAsyncStreamBaseTests.cs b/Kaitai.Struct.Runtime.Async.Tests/KaitaiAsyncStreamBaseTests.cs new file mode 100644 index 0000000..6af533a --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/KaitaiAsyncStreamBaseTests.cs @@ -0,0 +1,185 @@ +using System; +using System.IO; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Kaitai.Async; +using Xunit; + +namespace Kaitai.Struct.Runtime.Async.Tests +{ + public class StreamKaitaiAsyncStreamBaseTests : KaitaiAsyncStreamBaseTests + { + public StreamKaitaiAsyncStreamBaseTests() : base(false) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => new KaitaiAsyncStream(data); + } + + public class PipeReaderKaitaiAsyncStreamBaseTests : KaitaiAsyncStreamBaseTests + { + public PipeReaderKaitaiAsyncStreamBaseTests() : base(false) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => + new KaitaiAsyncStream(PipeReader.Create(new MemoryStream(data))); + } + + public class StreamKaitaiAsyncStreamBaseCancelledTests : KaitaiAsyncStreamBaseTests + { + public StreamKaitaiAsyncStreamBaseCancelledTests() : base(true) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => new KaitaiAsyncStream(data); + } + + public class PipeReaderKaitaiAsyncStreamBaseCancelledTests : KaitaiAsyncStreamBaseTests + { + public PipeReaderKaitaiAsyncStreamBaseCancelledTests() : base(true) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => + new KaitaiAsyncStream(PipeReader.Create(new MemoryStream(data))); + } + + public abstract class KaitaiAsyncStreamBaseTests : CancelableTestsBase + { + protected KaitaiAsyncStreamBaseTests(bool isTestingCancellation) : base(isTestingCancellation) + { + } + + protected abstract KaitaiAsyncStream Create(byte[] data); + + [Theory] + [InlineData(true, 0, 0)] + [InlineData(false, 1, 0)] + [InlineData(false, 1, 1)] + [InlineData(false, 1, 2)] + [InlineData(false, 1, 3)] + [InlineData(false, 1, 4)] + [InlineData(false, 1, 5)] + [InlineData(false, 1, 6)] + [InlineData(false, 1, 7)] + [InlineData(true, 1, 8)] + public async Task Eof_Test(bool shouldBeEof, int streamSize, int readBitsAmount) + { + var kaitaiStreamSUT = Create(new byte[streamSize]); + await EvaluateMaybeCancelled(async () => + { + await kaitaiStreamSUT.ReadBitsIntAsync(readBitsAmount); + long positionBeforeIsEof = kaitaiStreamSUT.Pos; + + if (shouldBeEof) + { + Assert.True(kaitaiStreamSUT.IsEof); + } + else + { + Assert.False(kaitaiStreamSUT.IsEof); + } + + Assert.Equal(positionBeforeIsEof, kaitaiStreamSUT.Pos); + }); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(1, 1)] + public async Task Pos_ByRead_Test(int expectedPos, int readBitsAmount) + { + var kaitaiStreamSUT = Create(new byte[1]); + + await EvaluateMaybeCancelled(async () => + { + await kaitaiStreamSUT.ReadBytesAsync(readBitsAmount); + + Assert.Equal(expectedPos, kaitaiStreamSUT.Pos); + }); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(1, 1)] + public async Task Pos_BySeek_Test(int expectedPos, int position) + { + var kaitaiStreamSUT = Create(new byte[1]); + + await EvaluateMaybeCancelled(async () => + { + await kaitaiStreamSUT.SeekAsync(position); + + Assert.Equal(expectedPos, kaitaiStreamSUT.Pos); + }); + } + + [Fact] + public async Task ForwardSeek_AfterReadToEndAndBackwardSeek_Test() + { + const int toRead = 1; + + var kaitaiStreamSUT = Create(new byte[2]); + + await EvaluateMaybeCancelled(async () => + { + // Simulates kaitai compiler generated code for multiple fields defined as `instances` + await kaitaiStreamSUT.ReadBytesFullAsync(CancellationToken.None); + await kaitaiStreamSUT.SeekAsync(0); + await kaitaiStreamSUT.SeekAsync(toRead); + + Assert.Equal(toRead, kaitaiStreamSUT.Pos); + }); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(100)] + [InlineData(1_000)] + [InlineData(10_000)] + [InlineData(100_000)] + [InlineData(1_000_000)] + public void Size_Test(int streamSize) + { + var kaitaiStreamSUT = Create(new byte[streamSize]); + long positionBeforeIsEof = kaitaiStreamSUT.Pos; + + Assert.Equal(streamSize, kaitaiStreamSUT.Size); + Assert.Equal(positionBeforeIsEof, kaitaiStreamSUT.Pos); + } + + [Fact] + public async Task AlignToByte_Test() + { + //Arrange + var kaitaiStreamSUT = Create(new byte[] {0b_1000_0000}); + + ulong read = await kaitaiStreamSUT.ReadBitsIntAsync(1); + Assert.Equal(1u, read); + + //Act + kaitaiStreamSUT.AlignToByte(); + //Assert + Assert.Equal(1, kaitaiStreamSUT.Pos); + } + + [Fact] + public void EmptyStream_NoRead_NoSeek_IsEof_ShouldBe_True() + { + var kaitaiStreamSUT = Create(new byte[0]); + + Assert.True(kaitaiStreamSUT.IsEof); + } + + [Fact] + public void EmptyStream_NoRead_NoSeek_Pos_ShouldBe_0() + { + var kaitaiStreamSUT = Create(new byte[0]); + + Assert.Equal(0, kaitaiStreamSUT.Pos); + } + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async.Tests/KaitaiAsyncStructTests.cs b/Kaitai.Struct.Runtime.Async.Tests/KaitaiAsyncStructTests.cs new file mode 100644 index 0000000..f64dc20 --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/KaitaiAsyncStructTests.cs @@ -0,0 +1,38 @@ +using System.IO; +using Kaitai.Async; +using Xunit; + +namespace Kaitai.Struct.Runtime.Async.Tests +{ + public class StreamKaitaiAsyncStructTests : KaitaiAsyncStructTests + { + protected override KaitaiAsyncStream Create(byte[] data) => new KaitaiAsyncStream(data); + } + + public class PipeReaderKaitaiAsyncStructTests : KaitaiAsyncStructTests + { + protected override KaitaiAsyncStream Create(byte[] data) => + new KaitaiAsyncStream(System.IO.Pipelines.PipeReader.Create(new MemoryStream(data))); + } + + public abstract class KaitaiAsyncStructTests + { + protected abstract KaitaiAsyncStream Create(byte[] data); + + private class FooKaitaiAsyncStruct : KaitaiAsyncStruct + { + public FooKaitaiAsyncStruct(KaitaiAsyncStream kaitaiStream) : base(kaitaiStream) + { + } + } + + [Fact] + public void M_Io_IsSet() + { + var kaitaiAsyncStream = Create(new byte[0]); + var kaitaiAsyncStructSUT = new FooKaitaiAsyncStruct(kaitaiAsyncStream); + + Assert.Equal(kaitaiAsyncStream, kaitaiAsyncStructSUT.M_Io); + } + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async.Tests/PipeReaderContextTests.cs b/Kaitai.Struct.Runtime.Async.Tests/PipeReaderContextTests.cs new file mode 100644 index 0000000..a28d9bd --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/PipeReaderContextTests.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Kaitai.Async; +using Xunit; + +namespace Kaitai.Struct.Runtime.Async.Tests +{ + public class PipeReaderContextTests + { + private readonly PipeReaderContext _pipeReaderContextSUT; + private readonly PipeWriter _pipeWriter; + + public PipeReaderContextTests() + { + var pipe = new Pipe(new PipeOptions(minimumSegmentSize: 2)); + _pipeWriter = pipe.Writer; + _pipeReaderContextSUT = new PipeReaderContext(pipe.Reader); + } + + [Fact] + public async Task ReadByteAsync_RequestingMoreData_ShouldAwaitOnRead() + { + //Arrange + await _pipeWriter.WriteAsync(new byte[1]); + + await _pipeReaderContextSUT.ReadByteAsync(CancellationToken.None); + using var cts = new CancellationTokenSource(100); + + //Act & assert + await Assert.ThrowsAsync( + async () => await _pipeReaderContextSUT.ReadByteAsync(cts.Token)); + } + + [Fact] + public async Task ReadBytesAsync_RequestingMoreData_ShouldAwaitOnRead() + { + //Arrange + await _pipeWriter.WriteAsync(new byte[2]); + + await _pipeReaderContextSUT.ReadBytesAsync(1, CancellationToken.None); + using var cts = new CancellationTokenSource(100); + + //Act & assert + await Assert.ThrowsAsync( + async () => await _pipeReaderContextSUT.ReadBytesAsync(2, cts.Token)); + } + + [Fact] + public async Task SeekAsync_PassEof_Completed_ShouldThrow() + { + //Arrange + await _pipeWriter.WriteAsync(new byte[1]); + await _pipeWriter.CompleteAsync(); + using var cts = new CancellationTokenSource(100); + + //Act & assert + await Assert.ThrowsAsync( + async () => await _pipeReaderContextSUT.SeekAsync(2, cts.Token)); + } + + [Fact] + public async Task SeekAsync_PassEof_NotCompleted_ShouldThrow() + { + //Arrange + await _pipeWriter.WriteAsync(new byte[1]); + using var cts = new CancellationTokenSource(100); + + //Act & assert + await Assert.ThrowsAsync( + async () => await _pipeReaderContextSUT.SeekAsync(2, cts.Token)); + } + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async.Tests/ReadBitsAsyncTests.cs b/Kaitai.Struct.Runtime.Async.Tests/ReadBitsAsyncTests.cs new file mode 100644 index 0000000..baa4b56 --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/ReadBitsAsyncTests.cs @@ -0,0 +1,41 @@ +using System.IO; +using System.Threading.Tasks; +using Kaitai.Async; +using Xunit; + +namespace Kaitai.Struct.Runtime.Async.Tests +{ + public class StreamKaitaiReadBitsAsyncTests : ReadBitsAsyncTests + { + protected override KaitaiAsyncStream Create(byte[] data) => new KaitaiAsyncStream(data); + } + + public class PipeReaderReadBitsAsyncTests : ReadBitsAsyncTests + { + protected override KaitaiAsyncStream Create(byte[] data) => + new KaitaiAsyncStream(System.IO.Pipelines.PipeReader.Create(new MemoryStream(data))); + } + + public abstract class ReadBitsAsyncTests + { + protected abstract KaitaiAsyncStream Create(byte[] data); + + [Theory] + [MemberData(nameof(BitsData.BitsBeData), MemberType = typeof(BitsData))] + public async Task ReadBitsIntAsync_Test(ulong expected, byte[] streamContent, int bitsCount) + { + var kaitaiStreamSUT = Create(streamContent); + + Assert.Equal(expected, await kaitaiStreamSUT.ReadBitsIntAsync(bitsCount)); + } + + [Theory] + [MemberData(nameof(BitsData.BitsLeData), MemberType = typeof(BitsData))] + public async Task ReadBitsIntLeAsync_Test(ulong expected, byte[] streamContent, int bitsCount) + { + var kaitaiStreamSUT = Create(streamContent); + + Assert.Equal(expected, await kaitaiStreamSUT.ReadBitsIntLeAsync(bitsCount)); + } + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async.Tests/ReadBytesAsyncTests.cs b/Kaitai.Struct.Runtime.Async.Tests/ReadBytesAsyncTests.cs new file mode 100644 index 0000000..6b903c2 --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/ReadBytesAsyncTests.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Kaitai.Async; +using Xunit; + +namespace Kaitai.Struct.Runtime.Async.Tests +{ + public class StreamReadBytesAsyncTests : ReadBytesAsyncTests + { + public StreamReadBytesAsyncTests() : base(false) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => new KaitaiAsyncStream(data); + } + + public class PipeReaderReadBytesAsyncTests : ReadBytesAsyncTests + { + public PipeReaderReadBytesAsyncTests() : base(false) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => + new KaitaiAsyncStream(PipeReader.Create(new MemoryStream(data))); + } + + public class StreamReadBytesAsyncCancelledTests : ReadBytesAsyncTests + { + public StreamReadBytesAsyncCancelledTests() : base(true) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => new KaitaiAsyncStream(data); + } + + public class PipeReaderReadBytesAsyncCancelledTests : ReadBytesAsyncTests + { + public PipeReaderReadBytesAsyncCancelledTests() : base(true) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => + new KaitaiAsyncStream(PipeReader.Create(new MemoryStream(data))); + } + + public abstract class ReadBytesAsyncTests : CancelableTestsBase + { + protected ReadBytesAsyncTests(bool isTestingCancellation) : base(isTestingCancellation) + { + } + + public static IEnumerable BytesData => + new List<(byte[] streamContent, int bytesCount)> + { + (new byte[] {0b_1101_0101}, 0), + (new byte[] {0b_1101_0101}, 1), + (new byte[] {0b_1101_0101, 0b_1101_0101}, 1), + (new byte[] {0b_1101_0101, 0b_1101_0101}, 2) + }.Select(t => new object[] {t.streamContent, t.bytesCount}); + + public static IEnumerable StringData => + new List + { + "", + "ABC", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + }.Select(t => new[] {Encoding.ASCII.GetBytes(t)}); + + public static IEnumerable StringWithTerminatorsData => + new List<(string streamContent, string expected, char terminator, bool isPresent, bool shouldInclude)> + { + ("", "", '\0', false, false), + ("", "", '\0', false, true), + + ("ABC", "ABC", '\0', false, false), + ("ABC", "ABC", '\0', false, true), + + ("ABC", "", 'A', true, false), + ("ABC", "A", 'A', true, true), + + ("ABC", "A", 'B', true, false), + ("ABC", "AB", 'B', true, true), + + ("ABC", "AB", 'C', true, false), + ("ABC", "ABC", 'C', true, true) + }.Select(t => new[] + { + Encoding.ASCII.GetBytes(t.streamContent), Encoding.ASCII.GetBytes(t.expected), (object) (byte) t.terminator, + t.isPresent, t.shouldInclude + }); + + protected abstract KaitaiAsyncStream Create(byte[] data); + + + [Theory] + [MemberData(nameof(BytesData))] + public async Task ReadBytesAsync_long_Test(byte[] streamContent, long bytesCount) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => + Assert.Equal(streamContent.Take((int) bytesCount), + await kaitaiStreamSUT.ReadBytesAsync(bytesCount, CancellationToken))); + } + + [Theory] + [MemberData(nameof(BytesData))] + public async Task ReadBytesAsync_ulong_Test(byte[] streamContent, ulong bytesCount) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => + Assert.Equal(streamContent.Take((int) bytesCount), + await kaitaiStreamSUT.ReadBytesAsync(bytesCount, CancellationToken))); + } + + + [Theory] + [MemberData(nameof(StringData))] + public async Task ReadBytesFullAsync_Test(byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => + Assert.Equal(streamContent, await kaitaiStreamSUT.ReadBytesFullAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(StringData))] + public async Task EnsureFixedContentsAsync_Test(byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => + Assert.Equal(streamContent, await kaitaiStreamSUT.EnsureFixedContentsAsync(streamContent, CancellationToken))); + } + + [Theory] + [MemberData(nameof(StringData))] + public async Task EnsureFixedContentsAsync_ThrowsIfByteIsChanged(byte[] streamContent) + { + if (streamContent.Length == 0) + { + return; + } + + var kaitaiStreamSUT = Create(streamContent); + + var expected = streamContent.ToArray(); + expected[0] = (byte) ~expected[0]; + + await Evaluate(async () => + await kaitaiStreamSUT.EnsureFixedContentsAsync(expected, CancellationToken)); + } + + [Theory] + [MemberData(nameof(StringWithTerminatorsData))] + public async Task ReadBytesTermAsync(byte[] streamContent, + byte[] expected, + byte terminator, + bool _, + bool shouldInclude) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => Assert.Equal(expected, + await kaitaiStreamSUT.ReadBytesTermAsync(terminator, shouldInclude, false, false, CancellationToken))); + } + + [Theory] + [MemberData(nameof(StringWithTerminatorsData))] + public async Task ReadBytesTermAsync_ThrowsIsTerminatorNotPresent(byte[] streamContent, + byte[] expected, + byte terminator, + bool terminatorIsPresent, + bool shouldInclude) + { + var kaitaiStreamSUT = Create(streamContent); + + if (terminatorIsPresent) + { + return; + } + + await Evaluate(async () => + await kaitaiStreamSUT.ReadBytesTermAsync(terminator, shouldInclude, false, true, CancellationToken)); + } + + [Theory] + [MemberData(nameof(StringWithTerminatorsData))] + public async Task ReadBytesTermAsync_ShouldNotConsumeTerminator(byte[] streamContent, + byte[] expected, + byte terminator, + bool terminatorIsPresent, + bool shouldInclude) + { + //Arrange + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => + { + //Act + await kaitaiStreamSUT.ReadBytesTermAsync(terminator, shouldInclude, false, false, CancellationToken); + + //Assert + int amountToConsume = expected.Length; + if (expected.Length > 0 && shouldInclude && terminatorIsPresent) + { + amountToConsume--; + } + + Assert.Equal(amountToConsume, kaitaiStreamSUT.Pos); + }); + } + + [Theory] + [MemberData(nameof(StringWithTerminatorsData))] + public async Task ReadBytesTermAsync_ShouldConsumeTerminator(byte[] streamContent, + byte[] expected, + byte terminator, + bool terminatorIsPresent, + bool shouldInclude) + { + //Arrange + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => + { + //Act + await kaitaiStreamSUT.ReadBytesTermAsync(terminator, shouldInclude, true, false, CancellationToken); + + //Assert + int amountToConsume = expected.Length; + if (!shouldInclude && terminatorIsPresent) + { + amountToConsume++; + } + + Assert.Equal(amountToConsume, kaitaiStreamSUT.Pos); + }); + } + + [Fact] + public async Task ReadBytesAsyncLong_LargerThanBufferInvoke_ThrowsArgumentOutOfRangeException() + { + var kaitaiStreamSUT = Create(new byte[0]); + + await Evaluate(async () => await kaitaiStreamSUT.ReadBytesAsync(1, CancellationToken)); + } + + [Fact] + public async Task ReadBytesAsyncLong_LargerThanInt32Invoke_ThrowsArgumentOutOfRangeException() + { + var kaitaiStreamSUT = Create(new byte[0]); + + await Evaluate(async () => + await kaitaiStreamSUT.ReadBytesAsync((long) int.MaxValue + 1, CancellationToken)); + } + + [Fact] + public async Task ReadBytesAsyncLong_NegativeInvoke_ThrowsArgumentOutOfRangeException() + { + var kaitaiStreamSUT = Create(new byte[0]); + + await Evaluate(async () => + await kaitaiStreamSUT.ReadBytesAsync(-1, CancellationToken)); + } + + [Fact] + public async Task ReadBytesAsyncULong_LargerThanBufferInvoke_ThrowsArgumentOutOfRangeException() + { + var kaitaiStreamSUT = Create(new byte[0]); + + await Evaluate(async () => + await kaitaiStreamSUT.ReadBytesAsync((ulong) 1, CancellationToken)); + } + + [Fact] + public async Task ReadBytesAsyncULong_LargerThanInt32Invoke_ThrowsArgumentOutOfRangeException() + { + var kaitaiStreamSUT = Create(new byte[0]); + + await Evaluate(async () => + await kaitaiStreamSUT.ReadBytesAsync((ulong) int.MaxValue + 1, CancellationToken)); + } + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async.Tests/ReadDecimalAsyncTests.cs b/Kaitai.Struct.Runtime.Async.Tests/ReadDecimalAsyncTests.cs new file mode 100644 index 0000000..3891dea --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/ReadDecimalAsyncTests.cs @@ -0,0 +1,92 @@ +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Threading.Tasks; +using Kaitai.Async; +using Xunit; + +namespace Kaitai.Struct.Runtime.Async.Tests +{ + public class StreamReadDecimalAsyncTests : ReadDecimalAsyncTests + { + public StreamReadDecimalAsyncTests() : base(false) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => new KaitaiAsyncStream(data); + } + + public class PipeReaderReadDecimalAsyncTests : ReadDecimalAsyncTests + { + public PipeReaderReadDecimalAsyncTests() : base(false) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => + new KaitaiAsyncStream(PipeReader.Create(new MemoryStream(data))); + } + + public class StreamReadDecimalAsyncCancelledTests : ReadDecimalAsyncTests + { + public StreamReadDecimalAsyncCancelledTests() : base(true) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => new KaitaiAsyncStream(data); + } + + public class PipeReaderReadDecimalAsyncCancelledTests : ReadDecimalAsyncTests + { + public PipeReaderReadDecimalAsyncCancelledTests() : base(true) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => + new KaitaiAsyncStream(PipeReader.Create(new MemoryStream(data))); + } + + public abstract class ReadDecimalAsyncTests : CancelableTestsBase + { + protected ReadDecimalAsyncTests(bool isTestingCancellation) : base(isTestingCancellation) + { + } + + protected abstract KaitaiAsyncStream Create(byte[] data); + + [Theory] + [MemberData(nameof(DecimalData.Decimal4Data), MemberType = typeof(DecimalData))] + public async Task ReadF4beAsync_Test(float expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent.Reverse().ToArray()); + + await Evaluate(async () => Assert.Equal(expected, await kaitaiStreamSUT.ReadF4beAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(DecimalData.Decimal4Data), MemberType = typeof(DecimalData))] + public async Task ReadF4leAsync_Test(float expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => Assert.Equal(expected, await kaitaiStreamSUT.ReadF4leAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(DecimalData.Decimal8Data), MemberType = typeof(DecimalData))] + public async Task ReadF8beAsync_Test(double expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent.Reverse().ToArray()); + + await Evaluate(async () => Assert.Equal(expected, await kaitaiStreamSUT.ReadF8beAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(DecimalData.Decimal8Data), MemberType = typeof(DecimalData))] + public async Task ReadF8leAsync_Test(double expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => Assert.Equal(expected, await kaitaiStreamSUT.ReadF8leAsync(CancellationToken))); + } + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async.Tests/ReadSignedAsyncTests.cs b/Kaitai.Struct.Runtime.Async.Tests/ReadSignedAsyncTests.cs new file mode 100644 index 0000000..b31f4c9 --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/ReadSignedAsyncTests.cs @@ -0,0 +1,119 @@ +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Threading.Tasks; +using Kaitai.Async; +using Xunit; + +namespace Kaitai.Struct.Runtime.Async.Tests +{ + public class StreamReadSignedAsyncTests : ReadSignedAsyncTests + { + public StreamReadSignedAsyncTests() : base(false) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => new KaitaiAsyncStream(data); + } + + public class PipeReaderReadSignedAsyncTests : ReadSignedAsyncTests + { + public PipeReaderReadSignedAsyncTests() : base(false) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => + new KaitaiAsyncStream(PipeReader.Create(new MemoryStream(data))); + } + + public class StreamReadSignedAsyncCancelledTests : ReadSignedAsyncTests + { + public StreamReadSignedAsyncCancelledTests() : base(true) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => new KaitaiAsyncStream(data); + } + + public class PipeReaderReadSignedAsyncCancelledTests : ReadSignedAsyncTests + { + public PipeReaderReadSignedAsyncCancelledTests() : base(true) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => + new KaitaiAsyncStream(PipeReader.Create(new MemoryStream(data))); + } + + public abstract class ReadSignedAsyncTests : CancelableTestsBase + { + protected ReadSignedAsyncTests(bool isTestingCancellation) : base(isTestingCancellation) + { + } + + protected abstract KaitaiAsyncStream Create(byte[] data); + + [Theory] + [MemberData(nameof(IntegralData.Integral1Data), MemberType = typeof(IntegralData))] + public async Task ReadS1Async_Test(sbyte expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async ()=>Assert.Equal(expected, await kaitaiStreamSUT.ReadS1Async(CancellationToken))); + } + + [Theory] + [MemberData(nameof(IntegralData.Integral2Data), MemberType = typeof(IntegralData))] + public async Task ReadS2beAsync_Test(short expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => Assert.Equal(expected, await kaitaiStreamSUT.ReadS2beAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(IntegralData.Integral4Data), MemberType = typeof(IntegralData))] + public async Task ReadS4beAsync_Test(int expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => Assert.Equal(expected, await kaitaiStreamSUT.ReadS4beAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(IntegralData.Integral8Data), MemberType = typeof(IntegralData))] + public async Task ReadS8beAsync_Test(long expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => Assert.Equal(expected, await kaitaiStreamSUT.ReadS8beAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(IntegralData.Integral2Data), MemberType = typeof(IntegralData))] + public async Task ReadS2leAsync_Test(short expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent.Reverse().ToArray()); + + await Evaluate(async () => Assert.Equal(expected, await kaitaiStreamSUT.ReadS2leAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(IntegralData.Integral4Data), MemberType = typeof(IntegralData))] + public async Task ReadS4leAsync_Test(int expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent.Reverse().ToArray()); + + await Evaluate(async () => Assert.Equal(expected, await kaitaiStreamSUT.ReadS4leAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(IntegralData.Integral8Data), MemberType = typeof(IntegralData))] + public async Task ReadS8leAsync_Test(long expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent.Reverse().ToArray()); + + await Evaluate(async () => Assert.Equal(expected, await kaitaiStreamSUT.ReadS8leAsync(CancellationToken))); + } + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async.Tests/ReadUnSignedAsyncTests.cs b/Kaitai.Struct.Runtime.Async.Tests/ReadUnSignedAsyncTests.cs new file mode 100644 index 0000000..2c0c441 --- /dev/null +++ b/Kaitai.Struct.Runtime.Async.Tests/ReadUnSignedAsyncTests.cs @@ -0,0 +1,126 @@ +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Threading.Tasks; +using Kaitai.Async; +using Xunit; + +namespace Kaitai.Struct.Runtime.Async.Tests +{ + public class StreamReadUnSignedAsyncTests : ReadUnSignedAsyncTests + { + public StreamReadUnSignedAsyncTests() : base(false) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => new KaitaiAsyncStream(data); + } + + public class StreamReadUnSignedAsyncCancelledTests : ReadUnSignedAsyncTests + { + public StreamReadUnSignedAsyncCancelledTests() : base(true) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => new KaitaiAsyncStream(data); + } + + public class PipeReaderReadUnSignedAsyncTests : ReadUnSignedAsyncTests + { + public PipeReaderReadUnSignedAsyncTests() : base(false) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => + new KaitaiAsyncStream(PipeReader.Create(new MemoryStream(data))); + } + + public class PipeReaderReadUnSignedAsyncCancelledTests : ReadUnSignedAsyncTests + { + public PipeReaderReadUnSignedAsyncCancelledTests() : base(true) + { + } + + protected override KaitaiAsyncStream Create(byte[] data) => + new KaitaiAsyncStream(PipeReader.Create(new MemoryStream(data))); + } + + + public abstract class ReadUnSignedAsyncTests : CancelableTestsBase + { + protected ReadUnSignedAsyncTests(bool isTestingCancellation) : base(isTestingCancellation) + { + } + + protected abstract KaitaiAsyncStream Create(byte[] data); + + [Theory] + [MemberData(nameof(IntegralData.Integral1Data), MemberType = typeof(IntegralData))] + public async Task ReadU1Async_Test( /*u*/ sbyte expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => Assert.Equal((byte) expected, await kaitaiStreamSUT.ReadU1Async(CancellationToken))); + } + + [Theory] + [MemberData(nameof(IntegralData.Integral2Data), MemberType = typeof(IntegralData))] + public async Task ReadU2beAsync_Test( /*u*/ short expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => + Assert.Equal((ushort) expected, await kaitaiStreamSUT.ReadU2beAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(IntegralData.Integral4Data), MemberType = typeof(IntegralData))] + public async Task ReadU4beAsync_Test( /*u*/ int expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate(async () => + Assert.Equal((uint) expected, await kaitaiStreamSUT.ReadU4beAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(IntegralData.Integral8Data), MemberType = typeof(IntegralData))] + public async Task ReadU8beAsync_Test( /*u*/ long expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent); + + await Evaluate( + async () => Assert.Equal((ulong) expected, await kaitaiStreamSUT.ReadU8beAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(IntegralData.Integral2Data), MemberType = typeof(IntegralData))] + public async Task ReadU2leAsync_Test( /*u*/ short expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent.Reverse().ToArray()); + + await Evaluate(async () => + Assert.Equal((ushort) expected, await kaitaiStreamSUT.ReadU2leAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(IntegralData.Integral4Data), MemberType = typeof(IntegralData))] + public async Task ReadU4leAsync_Test( /*u*/ int expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent.Reverse().ToArray()); + + await Evaluate(async () => + Assert.Equal((uint) expected, await kaitaiStreamSUT.ReadU4leAsync(CancellationToken))); + } + + [Theory] + [MemberData(nameof(IntegralData.Integral8Data), MemberType = typeof(IntegralData))] + public async Task ReadU8leAsync_Test( /*u*/ long expected, byte[] streamContent) + { + var kaitaiStreamSUT = Create(streamContent.Reverse().ToArray()); + + await Evaluate( + async () => Assert.Equal((ulong) expected, await kaitaiStreamSUT.ReadU8leAsync(CancellationToken))); + } + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async/Interface/IKaitaiAsyncStream.cs b/Kaitai.Struct.Runtime.Async/Interface/IKaitaiAsyncStream.cs new file mode 100644 index 0000000..220d43c --- /dev/null +++ b/Kaitai.Struct.Runtime.Async/Interface/IKaitaiAsyncStream.cs @@ -0,0 +1,186 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Kaitai.Async +{ + public interface IKaitaiAsyncStream : IKaitaiStreamBase + { + /// + /// Check if the stream position is at the end of the stream + /// + ValueTask IsEofAsync(CancellationToken cancellationToken = default); + + /// + /// Get the total length of the stream (ie. file size) + /// + ValueTask GetSizeAsync(CancellationToken cancellationToken = default); + + /// + /// Seek to a specific position from the beginning of the stream + /// + /// The position to seek to + /// + Task SeekAsync(long position, CancellationToken cancellationToken = default); + + /// + /// Read a signed byte from the stream + /// + /// + Task ReadS1Async(CancellationToken cancellationToken = default); + + /// + /// Read a signed short from the stream (big endian) + /// + /// + Task ReadS2beAsync(CancellationToken cancellationToken = default); + + /// + /// Read a signed int from the stream (big endian) + /// + /// + Task ReadS4beAsync(CancellationToken cancellationToken = default); + + /// + /// Read a signed long from the stream (big endian) + /// + /// + Task ReadS8beAsync(CancellationToken cancellationToken = default); + + /// + /// Read a signed short from the stream (little endian) + /// + /// + Task ReadS2leAsync(CancellationToken cancellationToken = default); + + /// + /// Read a signed int from the stream (little endian) + /// + /// + Task ReadS4leAsync(CancellationToken cancellationToken = default); + + /// + /// Read a signed long from the stream (little endian) + /// + /// + Task ReadS8leAsync(CancellationToken cancellationToken = default); + + /// + /// Read an unsigned byte from the stream + /// + /// + Task ReadU1Async(CancellationToken cancellationToken = default); + + /// + /// Read an unsigned short from the stream (big endian) + /// + /// + Task ReadU2beAsync(CancellationToken cancellationToken = default); + + /// + /// Read an unsigned int from the stream (big endian) + /// + /// + Task ReadU4beAsync(CancellationToken cancellationToken = default); + + /// + /// Read an unsigned long from the stream (big endian) + /// + /// + Task ReadU8beAsync(CancellationToken cancellationToken = default); + + /// + /// Read an unsigned short from the stream (little endian) + /// + /// + Task ReadU2leAsync(CancellationToken cancellationToken = default); + + /// + /// Read an unsigned int from the stream (little endian) + /// + /// + Task ReadU4leAsync(CancellationToken cancellationToken = default); + + /// + /// Read an unsigned long from the stream (little endian) + /// + /// + Task ReadU8leAsync(CancellationToken cancellationToken = default); + + /// + /// Read a single-precision floating point value from the stream (big endian) + /// + /// + Task ReadF4beAsync(CancellationToken cancellationToken = default); + + /// + /// Read a double-precision floating point value from the stream (big endian) + /// + /// + Task ReadF8beAsync(CancellationToken cancellationToken = default); + + /// + /// Read a single-precision floating point value from the stream (little endian) + /// + /// + Task ReadF4leAsync(CancellationToken cancellationToken = default); + + /// + /// Read a double-precision floating point value from the stream (little endian) + /// + /// + Task ReadF8leAsync(CancellationToken cancellationToken = default); + + [Obsolete("use ReadBitsIntBe instead")] + Task ReadBitsIntAsync(int n, CancellationToken cancellationToken = default); + + Task ReadBitsIntBeAsync(int n, CancellationToken cancellationToken = default); + + Task ReadBitsIntLeAsync(int n, CancellationToken cancellationToken = default); + + /// + /// Read a fixed number of bytes from the stream + /// + /// The number of bytes to read + /// + /// + Task ReadBytesAsync(long count, CancellationToken cancellationToken = default); + + /// + /// Read a fixed number of bytes from the stream + /// + /// The number of bytes to read + /// + /// + Task ReadBytesAsync(ulong count, CancellationToken cancellationToken = default); + + /// + /// Read all the remaining bytes from the stream until the end is reached + /// + /// + Task ReadBytesFullAsync(CancellationToken cancellationToken = default); + + /// + /// Read a terminated string from the stream + /// + /// The string terminator value + /// True to include the terminator in the returned string + /// True to consume the terminator byte before returning + /// True to throw an error when the EOS was reached before the terminator + /// + Task ReadBytesTermAsync(byte terminator, + bool includeTerminator, + bool consumeTerminator, + bool eosError, + CancellationToken cancellationToken = default); + + /// + /// Read a specific set of bytes and assert that they are the same as an expected result + /// + /// The expected result + /// + /// + [Obsolete("use explicit \"if\" using ByteArrayCompare method instead")] + Task EnsureFixedContentsAsync(byte[] expected, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async/Interface/IReaderContext.cs b/Kaitai.Struct.Runtime.Async/Interface/IReaderContext.cs new file mode 100644 index 0000000..d497968 --- /dev/null +++ b/Kaitai.Struct.Runtime.Async/Interface/IReaderContext.cs @@ -0,0 +1,16 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Kaitai.Async +{ + public interface IReaderContext + { + long Position { get; } + ValueTask GetSizeAsync(CancellationToken cancellationToken = default); + ValueTask IsEofAsync(CancellationToken cancellationToken = default); + ValueTask SeekAsync(long position, CancellationToken cancellationToken = default); + ValueTask ReadByteAsync(CancellationToken cancellationToken = default); + ValueTask ReadBytesAsync(long count, CancellationToken cancellationToken = default); + ValueTask ReadBytesFullAsync(CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async/Kaitai.Struct.Runtime.Async.csproj b/Kaitai.Struct.Runtime.Async/Kaitai.Struct.Runtime.Async.csproj new file mode 100644 index 0000000..a842e82 --- /dev/null +++ b/Kaitai.Struct.Runtime.Async/Kaitai.Struct.Runtime.Async.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + Kaitai.Async + + + + + + + + + + + + diff --git a/Kaitai.Struct.Runtime.Async/Kaitai.Struct.Runtime.Async.csproj.DotSettings b/Kaitai.Struct.Runtime.Async/Kaitai.Struct.Runtime.Async.csproj.DotSettings new file mode 100644 index 0000000..ad21594 --- /dev/null +++ b/Kaitai.Struct.Runtime.Async/Kaitai.Struct.Runtime.Async.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async/KaitaiAsyncStream.cs b/Kaitai.Struct.Runtime.Async/KaitaiAsyncStream.cs new file mode 100644 index 0000000..6813198 --- /dev/null +++ b/Kaitai.Struct.Runtime.Async/KaitaiAsyncStream.cs @@ -0,0 +1,360 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; + +namespace Kaitai.Async +{ + public class KaitaiAsyncStream : KaitaiStreamBase, IKaitaiAsyncStream + { + protected readonly IReaderContext ReaderContext; + private ulong _bits; + private int _bitsLeft; + + #region Constructors + + public KaitaiAsyncStream(IReaderContext readerContext) + { + ReaderContext = readerContext; + } + + public KaitaiAsyncStream(PipeReader pipeReader) + { + ReaderContext = new PipeReaderContext(pipeReader); + } + + public KaitaiAsyncStream(Stream stream) + { + ReaderContext = new StreamReaderContext(stream); + } + + /// + /// Creates a IKaitaiAsyncStream backed by a file (RO) + /// + public KaitaiAsyncStream(string file) : this(File.Open(file, + FileMode.Open, + FileAccess.Read, + FileShare.Read)) + { + } + + /// + ///Creates a IKaitaiAsyncStream backed by a byte buffer + /// + public KaitaiAsyncStream(byte[] bytes) : this(new MemoryStream(bytes)) + { + } + + #endregion + + #region Stream positioning + + public override bool IsEof => + ReaderContext.IsEofAsync().GetAwaiter().GetResult() && _bitsLeft == 0; + + public async ValueTask IsEofAsync(CancellationToken cancellationToken = default) => await ReaderContext.IsEofAsync(cancellationToken) && _bitsLeft == 0; + + public ValueTask GetSizeAsync(CancellationToken cancellationToken = default) => ReaderContext.GetSizeAsync(cancellationToken); + + public virtual async Task SeekAsync(long position, CancellationToken cancellationToken = default) => await ReaderContext.SeekAsync(position, cancellationToken); + public virtual async Task SeekAsync(ulong position, CancellationToken cancellationToken = default) => await SeekAsync((long)position, cancellationToken); + + public override long Pos => ReaderContext.Position; + + public override long Size => ReaderContext.GetSizeAsync().GetAwaiter().GetResult(); + + #endregion + + #region Integer types + + #region Signed + + public async Task ReadS1Async(CancellationToken cancellationToken = default) => (sbyte) await ReadU1Async(cancellationToken); + + #region Big-endian + + public async Task ReadS2beAsync(CancellationToken cancellationToken = default) => BitConverter.ToInt16(await ReadBytesNormalisedBigEndianAsync(2, cancellationToken), 0); + + public async Task ReadS4beAsync(CancellationToken cancellationToken = default) => BitConverter.ToInt32(await ReadBytesNormalisedBigEndianAsync(4, cancellationToken), 0); + + public async Task ReadS8beAsync(CancellationToken cancellationToken = default) => BitConverter.ToInt64(await ReadBytesNormalisedBigEndianAsync(8, cancellationToken), 0); + + #endregion + + #region Little-endian + + public async Task ReadS2leAsync(CancellationToken cancellationToken = default) => BitConverter.ToInt16(await ReadBytesNormalisedLittleEndianAsync(2, cancellationToken), 0); + + public async Task ReadS4leAsync(CancellationToken cancellationToken = default) => BitConverter.ToInt32(await ReadBytesNormalisedLittleEndianAsync(4, cancellationToken), 0); + + public async Task ReadS8leAsync(CancellationToken cancellationToken = default) => BitConverter.ToInt64(await ReadBytesNormalisedLittleEndianAsync(8, cancellationToken), 0); + + #endregion + + #endregion + + #region Unsigned + + public async Task ReadU1Async(CancellationToken cancellationToken = default) => await ReaderContext.ReadByteAsync(cancellationToken); + + #region Big-endian + + public async Task ReadU2beAsync(CancellationToken cancellationToken = default) => BitConverter.ToUInt16(await ReadBytesNormalisedBigEndianAsync(2, cancellationToken), 0); + + public async Task ReadU4beAsync(CancellationToken cancellationToken = default) => BitConverter.ToUInt32(await ReadBytesNormalisedBigEndianAsync(4, cancellationToken), 0); + + public async Task ReadU8beAsync(CancellationToken cancellationToken = default) => BitConverter.ToUInt64(await ReadBytesNormalisedBigEndianAsync(8, cancellationToken), 0); + + #endregion + + #region Little-endian + + public async Task ReadU2leAsync(CancellationToken cancellationToken = default) => + BitConverter.ToUInt16(await ReadBytesNormalisedLittleEndianAsync(2, cancellationToken), 0); + + public async Task ReadU4leAsync(CancellationToken cancellationToken = default) => BitConverter.ToUInt32(await ReadBytesNormalisedLittleEndianAsync(4, cancellationToken), 0); + + public async Task ReadU8leAsync(CancellationToken cancellationToken = default) => BitConverter.ToUInt64(await ReadBytesNormalisedLittleEndianAsync(8, cancellationToken), 0); + + #endregion + + #endregion + + #endregion + + #region Floating point types + + #region Big-endian + + public async Task ReadF4beAsync(CancellationToken cancellationToken = default) => BitConverter.ToSingle(await ReadBytesNormalisedBigEndianAsync(4, cancellationToken), 0); + + public async Task ReadF8beAsync(CancellationToken cancellationToken = default) => BitConverter.ToDouble(await ReadBytesNormalisedBigEndianAsync(8, cancellationToken), 0); + + #endregion + + #region Little-endian + + public async Task ReadF4leAsync(CancellationToken cancellationToken = default) => BitConverter.ToSingle(await ReadBytesNormalisedLittleEndianAsync(4, cancellationToken), 0); + + public async Task ReadF8leAsync(CancellationToken cancellationToken = default) => + BitConverter.ToDouble(await ReadBytesNormalisedLittleEndianAsync(8, cancellationToken), 0); + + #endregion + + #endregion + + #region Unaligned bit values + + public override void AlignToByte() + { + _bits = 0; + _bitsLeft = 0; + } + + public async Task ReadBitsIntBeAsync(int n, CancellationToken cancellationToken = default) + { + int bitsNeeded = n - _bitsLeft; + if (bitsNeeded > 0) + { + // 1 bit => 1 byte + // 8 bits => 1 byte + // 9 bits => 2 bytes + int bytesNeeded = (bitsNeeded - 1) / 8 + 1; + var buf = await ReadBytesAsync(bytesNeeded, cancellationToken); + for (var i = 0; i < buf.Length; i++) + { + _bits <<= 8; + _bits |= buf[i]; + _bitsLeft += 8; + } + } + + // raw mask with required number of 1s, starting from lowest bit + ulong mask = GetMaskOnes(n); + // shift "bits" to align the highest bits with the mask & derive reading result + int shiftBits = _bitsLeft - n; + ulong res = (_bits >> shiftBits) & mask; + // clear top bits that we've just read => AND with 1s + _bitsLeft -= n; + mask = GetMaskOnes(_bitsLeft); + _bits &= mask; + + return res; + } + + public Task ReadBitsIntAsync(int n, CancellationToken cancellationToken = default) => + ReadBitsIntBeAsync(n, cancellationToken); + + + //Method ported from algorithm specified @ issue#155 + public async Task ReadBitsIntLeAsync(int n, CancellationToken cancellationToken = default) + { + int bitsNeeded = n - _bitsLeft; + + if (bitsNeeded > 0) + { + // 1 bit => 1 byte + // 8 bits => 1 byte + // 9 bits => 2 bytes + int bytesNeeded = (bitsNeeded - 1) / 8 + 1; + var buf = await ReadBytesAsync(bytesNeeded, cancellationToken); + for (var i = 0; i < buf.Length; i++) + { + ulong v = (ulong) buf[i] << _bitsLeft; + _bits |= v; + _bitsLeft += 8; + } + } + + // raw mask with required number of 1s, starting from lowest bit + ulong mask = GetMaskOnes(n); + + // derive reading result + ulong res = _bits & mask; + + // remove bottom bits that we've just read by shifting + _bits >>= n; + _bitsLeft -= n; + + return res; + } + + #endregion + + #region Byte arrays + + public async Task ReadBytesAsync(long count, CancellationToken cancellationToken = default) => await ReaderContext.ReadBytesAsync(count, cancellationToken); + + public async Task ReadBytesAsync(ulong count, CancellationToken cancellationToken = default) + { + if (count > int.MaxValue) + { + throw new ArgumentOutOfRangeException( + $"requested {count} bytes, while only non-negative int32 amount of bytes possible"); + } + + return await ReadBytesAsync((long) count, cancellationToken); + } + + /// + /// Read bytes from the stream in little endian format and convert them to the endianness of the current platform + /// + /// The number of bytes to read + /// An array of bytes that matches the endianness of the current platform + protected async Task ReadBytesNormalisedLittleEndianAsync(int count, CancellationToken cancellationToken = default) + { + var bytes = await ReadBytesAsync(count, cancellationToken); + if (!IsLittleEndian) + { + Array.Reverse(bytes); + } + + return bytes; + } + + /// + /// Read bytes from the stream in big endian format and convert them to the endianness of the current platform + /// + /// The number of bytes to read + /// An array of bytes that matches the endianness of the current platform + protected async Task ReadBytesNormalisedBigEndianAsync(int count, CancellationToken cancellationToken = default) + { + var bytes = await ReadBytesAsync(count, cancellationToken); + if (IsLittleEndian) + { + Array.Reverse(bytes); + } + + return bytes; + } + + /// + /// Read all the remaining bytes from the stream until the end is reached + /// + /// + public virtual async Task ReadBytesFullAsync(CancellationToken cancellationToken = default) => await ReaderContext.ReadBytesFullAsync(cancellationToken); + + /// + /// Read a terminated string from the stream + /// + /// The string terminator value + /// True to include the terminator in the returned string + /// True to consume the terminator byte before returning + /// True to throw an error when the EOS was reached before the terminator + /// + /// + public async Task ReadBytesTermAsync(byte terminator, + bool includeTerminator, + bool consumeTerminator, + bool eosError, + CancellationToken cancellationToken = default) + { + var bytes = new List(); + while (true) + { + if (await IsEofAsync(cancellationToken)) + { + if (eosError) + { + throw new EndOfStreamException( + $"End of stream reached, but no terminator `{terminator}` found"); + } + + break; + } + + byte b = await ReadU1Async(cancellationToken); + if (b == terminator) + { + if (includeTerminator) + { + bytes.Add(b); + } + + if (!consumeTerminator) + { + await SeekAsync(Pos - 1, cancellationToken); + } + + break; + } + + bytes.Add(b); + } + + return bytes.ToArray(); + } + + /// + /// Read a specific set of bytes and assert that they are the same as an expected result + /// + /// The expected result + /// + /// + public async Task EnsureFixedContentsAsync(byte[] expected, CancellationToken cancellationToken = default) + { + var bytes = await ReadBytesAsync(expected.Length, cancellationToken); + + if (bytes.Length != expected.Length) //TODO Is this necessary? + { + throw new Exception( + $"Expected bytes: {Convert.ToBase64String(expected)} ({expected.Length} bytes), Instead got: {Convert.ToBase64String(bytes)} ({bytes.Length} bytes)"); + } + + for (var i = 0; i < bytes.Length; i++) + { + if (bytes[i] != expected[i]) + { + throw new Exception( + $"Expected bytes: {Convert.ToBase64String(expected)} ({expected.Length} bytes), Instead got: {Convert.ToBase64String(bytes)} ({bytes.Length} bytes)"); + } + } + + return bytes; + } + + #endregion + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async/KaitaiAsyncStruct.cs b/Kaitai.Struct.Runtime.Async/KaitaiAsyncStruct.cs new file mode 100644 index 0000000..faf626d --- /dev/null +++ b/Kaitai.Struct.Runtime.Async/KaitaiAsyncStruct.cs @@ -0,0 +1,14 @@ +namespace Kaitai.Async +{ + public abstract class KaitaiAsyncStruct + { + protected readonly KaitaiAsyncStream m_io; + + protected KaitaiAsyncStruct(KaitaiAsyncStream kaitaiStream) + { + m_io = kaitaiStream; + } + + public KaitaiAsyncStream M_Io => m_io; + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async/ReaderContext/PipeReaderContext.cs b/Kaitai.Struct.Runtime.Async/ReaderContext/PipeReaderContext.cs new file mode 100644 index 0000000..600f1aa --- /dev/null +++ b/Kaitai.Struct.Runtime.Async/ReaderContext/PipeReaderContext.cs @@ -0,0 +1,166 @@ +using System; +using System.Buffers; +using System.IO; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; + +namespace Kaitai.Async +{ + public class PipeReaderContext : IReaderContext + { + protected readonly PipeReader PipeReader; + protected ReadResult ReadResult; + + public PipeReaderContext(PipeReader pipeReader) + { + PipeReader = pipeReader; + } + + protected long RemainingBytesInReadResult => ReadResult.Buffer.Length - Position; + + public long Position { get; protected set; } + + public virtual async ValueTask GetSizeAsync(CancellationToken cancellationToken = default) + { + await FillReadResultBufferToTheEndAsync(cancellationToken); + + return ReadResult.Buffer.Length; + } + + public virtual async ValueTask IsEofAsync(CancellationToken cancellationToken = default) + { + await EnsureReadResultIsNotDefaultAsync(cancellationToken); + + if (Position >= ReadResult.Buffer.Length && !ReadResult.IsCompleted) + { + PipeReader.AdvanceTo(ReadResult.Buffer.Start, ReadResult.Buffer.GetPosition(Position)); + ReadResult = await PipeReader.ReadAsync(cancellationToken); + } + + return Position >= ReadResult.Buffer.Length && ReadResult.IsCompleted; + } + + + public virtual async ValueTask SeekAsync(long position, CancellationToken cancellationToken = default) + { + if (position <= Position) + { + Position = position; + } + else + { + await EnsureReadResultIsNotDefaultAsync(cancellationToken); + + while (ReadResult.Buffer.Length < position && !ReadResult.IsCompleted) + { + PipeReader.AdvanceTo(ReadResult.Buffer.Start, ReadResult.Buffer.End); + ReadResult = await PipeReader.ReadAsync(cancellationToken); + } + + if (ReadResult.Buffer.Length >= position) + { + Position = position; + return; + } + + if (ReadResult.IsCompleted) + { + throw new EndOfStreamException( + $"requested {position} bytes, but got only {RemainingBytesInReadResult} bytes"); + } + } + } + + public virtual async ValueTask ReadByteAsync(CancellationToken cancellationToken = default) + { + await EnsureReadResultIsNotDefaultAsync(cancellationToken); + + var value = byte.MinValue; + while (!TryReadByte(out value) && !ReadResult.IsCompleted) + { + PipeReader.AdvanceTo(ReadResult.Buffer.Start, ReadResult.Buffer.End); + ReadResult = await PipeReader.ReadAsync(cancellationToken); + } + + Position += 1; + return value; + + bool TryReadByte(out byte readValue) + { + var sequenceReader = new SequenceReader(ReadResult.Buffer); + sequenceReader.Advance(Position); + return sequenceReader.TryRead(out readValue); + } + } + + public virtual async ValueTask ReadBytesAsync(long count, CancellationToken cancellationToken = default) + { + if (count < 0 || count > int.MaxValue) + { + throw new ArgumentOutOfRangeException( + $"requested {count} bytes, while only non-negative int32 amount of bytes possible"); + } + + await EnsureReadResultIsNotDefaultAsync(cancellationToken); + + byte[] value = null; + + while (!TryRead(out value, count)) + { + if (ReadResult.IsCompleted) + { + throw new EndOfStreamException( + $"requested {count} bytes, but got only {RemainingBytesInReadResult} bytes"); + } + + PipeReader.AdvanceTo(ReadResult.Buffer.Start, ReadResult.Buffer.End); + ReadResult = await PipeReader.ReadAsync(cancellationToken); + } + + Position += count; + return value; + + bool TryRead(out byte[] readBytes, long readBytesCount) + { + if (RemainingBytesInReadResult < readBytesCount) + { + readBytes = null; + return false; + } + + readBytes = ReadResult.Buffer.Slice(Position, readBytesCount).ToArray(); + return true; + } + } + + public virtual async ValueTask ReadBytesFullAsync(CancellationToken cancellationToken = default) + { + await FillReadResultBufferToTheEndAsync(cancellationToken); + + PipeReader.AdvanceTo(ReadResult.Buffer.Start, ReadResult.Buffer.End); + var value = ReadResult.Buffer.Slice(Position, ReadResult.Buffer.End).ToArray(); + Position += value.Length; + return value; + } + + private async ValueTask FillReadResultBufferToTheEndAsync(CancellationToken cancellationToken = default) + { + await EnsureReadResultIsNotDefaultAsync(cancellationToken); + + while (!ReadResult.IsCompleted) + { + PipeReader.AdvanceTo(ReadResult.Buffer.Start, ReadResult.Buffer.End); + ReadResult = await PipeReader.ReadAsync(cancellationToken); + } + } + + private async ValueTask EnsureReadResultIsNotDefaultAsync(CancellationToken cancellationToken = default) + { + if (ReadResult.Equals(default(ReadResult))) + { + ReadResult = await PipeReader.ReadAsync(cancellationToken); + } + } + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime.Async/ReaderContext/StreamReaderContext.cs b/Kaitai.Struct.Runtime.Async/ReaderContext/StreamReaderContext.cs new file mode 100644 index 0000000..6eeddf5 --- /dev/null +++ b/Kaitai.Struct.Runtime.Async/ReaderContext/StreamReaderContext.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Overby.Extensions.AsyncBinaryReaderWriter; + +namespace Kaitai.Async +{ + public class StreamReaderContext : IReaderContext + { + private readonly Stream _baseStream; + protected readonly AsyncBinaryReader AsyncBinaryReader; + + public StreamReaderContext(Stream stream) + { + _baseStream = stream; + AsyncBinaryReader = new AsyncBinaryReader(_baseStream); + } + + public long Position => _baseStream.Position; + + public virtual async ValueTask GetSizeAsync(CancellationToken cancellationToken = default) + { + await CheckIsCancellationRequested(cancellationToken); + + return _baseStream.Length; + } + + public virtual async ValueTask IsEofAsync(CancellationToken cancellationToken = default) + { + await CheckIsCancellationRequested(cancellationToken); + + return _baseStream.Position >= _baseStream.Length; + } + + public virtual async ValueTask SeekAsync(long position, CancellationToken cancellationToken = default) + { + await CheckIsCancellationRequested(cancellationToken); + + _baseStream.Seek(position, SeekOrigin.Begin); + } + + public virtual async ValueTask ReadByteAsync(CancellationToken cancellationToken = default) => + (byte) await AsyncBinaryReader.ReadSByteAsync(cancellationToken); + + public virtual async ValueTask ReadBytesAsync(long count, CancellationToken cancellationToken = default) + { + if (count < 0 || count > int.MaxValue) + { + throw new ArgumentOutOfRangeException( + $"requested {count} bytes, while only non-negative int32 amount of bytes possible"); + } + + await CheckIsCancellationRequested(cancellationToken); + + var bytes = await AsyncBinaryReader.ReadBytesAsync((int) count, cancellationToken); + if (bytes.Length < count) + { + throw new EndOfStreamException($"requested {count} bytes, but got only {bytes.Length} bytes"); + } + + return bytes; + } + + public virtual async ValueTask ReadBytesFullAsync(CancellationToken cancellationToken = default) => + await ReadBytesAsync(_baseStream.Length - _baseStream.Position, cancellationToken); + + private static async Task CheckIsCancellationRequested(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + await Task.FromCanceled(cancellationToken); + } + } + } +} \ No newline at end of file diff --git a/Kaitai.Struct.Runtime/.editorconfig b/Kaitai.Struct.Runtime/.editorconfig new file mode 100644 index 0000000..43cdaa5 --- /dev/null +++ b/Kaitai.Struct.Runtime/.editorconfig @@ -0,0 +1,155 @@ +# Source: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference?view=vs-2019 + +############################### +# Core EditorConfig Options # +############################### + +root = true + +# All files +[*] +indent_style = space + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom + +############################### +# .NET Coding Conventions # +############################### + +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +# this. preferences +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent + +############################### +# Naming Conventions # +############################### + +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +############################### +# C# Code Style Rules # +############################### + +[*.cs] +# var preferences +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent + +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent + +# Pattern-matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +############################### +# C# Formatting Rules # +############################### + +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_after_comma = true +csharp_space_after_dot = false + +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true + +################################## +# Visual Basic Code Style Rules # +################################## + +[*.vb] +# Modifier preferences +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion diff --git a/Kaitai.Struct.Runtime/Exception/KaitaiStructError.cs b/Kaitai.Struct.Runtime/Exception/KaitaiStructError.cs new file mode 100644 index 0000000..4a28cb5 --- /dev/null +++ b/Kaitai.Struct.Runtime/Exception/KaitaiStructError.cs @@ -0,0 +1,20 @@ +using System; + +namespace Kaitai +{ + /// + /// Common ancestor for all error originating from Kaitai Struct usage. + /// Stores KSY source path, pointing to an element supposedly guilty of + /// an error. + /// + public class KaitaiStructError : Exception + { + protected string srcPath; + + public KaitaiStructError(string msg, string srcPath) + : base($"srcPath: {msg}") + { + this.srcPath = srcPath; + } + } +} diff --git a/Kaitai.Struct.Runtime/Exception/UndecidedEndiannessError.cs b/Kaitai.Struct.Runtime/Exception/UndecidedEndiannessError.cs new file mode 100644 index 0000000..b2ef825 --- /dev/null +++ b/Kaitai.Struct.Runtime/Exception/UndecidedEndiannessError.cs @@ -0,0 +1,24 @@ +using System; + +namespace Kaitai +{ + /// + /// Error that occurs when default endianness should be decided with a + /// switch, but nothing matches (although using endianness expression + /// implies that there should be some positive result). + /// + public class UndecidedEndiannessError : Exception { + public UndecidedEndiannessError() + : base("Unable to decide on endianness") + { + } + public UndecidedEndiannessError(string msg) + : base(msg) + { + } + public UndecidedEndiannessError(string msg, Exception inner) + : base(msg, inner) + { + } + } +} diff --git a/Kaitai.Struct.Runtime/Exception/ValidationExprError.cs b/Kaitai.Struct.Runtime/Exception/ValidationExprError.cs new file mode 100644 index 0000000..6d4030d --- /dev/null +++ b/Kaitai.Struct.Runtime/Exception/ValidationExprError.cs @@ -0,0 +1,14 @@ +using System; + +namespace Kaitai +{ + public class ValidationExprError : ValidationFailedError { + public ValidationExprError(Object actual, IKaitaiStreamBase io, string srcPath) + : base("not matching the expression, got " + actual, io, srcPath) + { + this.actual = actual; + } + + protected Object actual; + } +} diff --git a/Kaitai.Struct.Runtime/Exception/ValidationFailedError.cs b/Kaitai.Struct.Runtime/Exception/ValidationFailedError.cs new file mode 100644 index 0000000..373af24 --- /dev/null +++ b/Kaitai.Struct.Runtime/Exception/ValidationFailedError.cs @@ -0,0 +1,36 @@ +using System.Text; + +namespace Kaitai +{ + /// + /// Common ancestor for all validation failures. Stores pointer to + /// KaitaiStream IO object which was involved in an error. + /// + public class ValidationFailedError : KaitaiStructError + { + protected IKaitaiStreamBase io; + + public ValidationFailedError(string msg, IKaitaiStreamBase io, string srcPath) + : base($"at pos {io.Pos}: validation failed: {msg}", srcPath) + { + this.io = io; + } + + protected static string ByteArrayToHex(byte[] arr) + { + StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < arr.Length; i++) + { + if (i > 0) + { + sb.Append(' '); + } + + sb.Append($"{arr[i]:X2}"); + } + + sb.Append(']'); + return sb.ToString(); + } + } +} diff --git a/Kaitai.Struct.Runtime/Exception/ValidationGreaterThanError.cs b/Kaitai.Struct.Runtime/Exception/ValidationGreaterThanError.cs new file mode 100644 index 0000000..71cb3ad --- /dev/null +++ b/Kaitai.Struct.Runtime/Exception/ValidationGreaterThanError.cs @@ -0,0 +1,23 @@ +using System; + +namespace Kaitai +{ + public class ValidationGreaterThanError : ValidationFailedError { + public ValidationGreaterThanError(byte[] max, byte[] actual, IKaitaiStreamBase io, string srcPath) + : base("not in range, max " + ByteArrayToHex(max) + ", but got " + ByteArrayToHex(actual), io, srcPath) + { + this.max = max; + this.actual = actual; + } + + public ValidationGreaterThanError(Object max, Object actual, IKaitaiStreamBase io, string srcPath) + : base("not in range, max " + max + ", but got " + actual, io, srcPath) + { + this.max = max; + this.actual = actual; + } + + protected Object max; + protected Object actual; + } +} diff --git a/Kaitai.Struct.Runtime/Exception/ValidationLessThanError.cs b/Kaitai.Struct.Runtime/Exception/ValidationLessThanError.cs new file mode 100644 index 0000000..2b3383e --- /dev/null +++ b/Kaitai.Struct.Runtime/Exception/ValidationLessThanError.cs @@ -0,0 +1,22 @@ +using System; + +namespace Kaitai +{ + public class ValidationLessThanError : ValidationFailedError { + public ValidationLessThanError(byte[] min, byte[] actual, IKaitaiStreamBase io, string srcPath) + : base("not in range, min " + ByteArrayToHex(min) + ", but got " + ByteArrayToHex(actual), io, srcPath) + { + this.min = min; + this.actual = actual; + } + public ValidationLessThanError(Object min, Object actual, IKaitaiStreamBase io, string srcPath) + : base("not in range, min " + min + ", but got " + actual, io, srcPath) + { + this.min = min; + this.actual = actual; + } + + protected Object min; + protected Object actual; + } +} diff --git a/Kaitai.Struct.Runtime/Exception/ValidationNotAnyOfError.cs b/Kaitai.Struct.Runtime/Exception/ValidationNotAnyOfError.cs new file mode 100644 index 0000000..d8e0a4f --- /dev/null +++ b/Kaitai.Struct.Runtime/Exception/ValidationNotAnyOfError.cs @@ -0,0 +1,14 @@ +using System; + +namespace Kaitai +{ + public class ValidationNotAnyOfError : ValidationFailedError { + public ValidationNotAnyOfError(Object actual, IKaitaiStreamBase io, string srcPath) + : base("not any of the list, got " + actual, io, srcPath) + { + this.actual = actual; + } + + protected Object actual; + } +} diff --git a/Kaitai.Struct.Runtime/Exception/ValidationNotEqualError.cs b/Kaitai.Struct.Runtime/Exception/ValidationNotEqualError.cs new file mode 100644 index 0000000..e97520e --- /dev/null +++ b/Kaitai.Struct.Runtime/Exception/ValidationNotEqualError.cs @@ -0,0 +1,30 @@ +using System; + +namespace Kaitai +{ + /// + /// Signals validation failure: we required "actual" value to be equal to + /// "expected", but it turned out that it's not. + /// + public class ValidationNotEqualError : ValidationFailedError + { + protected Object actual; + + protected Object expected; + + public ValidationNotEqualError(byte[] expected, byte[] actual, IKaitaiStreamBase io, string srcPath) + : base($"not equal, expected {ByteArrayToHex(expected)}, but got {ByteArrayToHex(actual)}", io, + srcPath) + { + this.expected = expected; + this.actual = actual; + } + + public ValidationNotEqualError(Object expected, Object actual, IKaitaiStreamBase io, string srcPath) + : base($"not equal, expected {expected}, but got {actual}", io, srcPath) + { + this.expected = expected; + this.actual = actual; + } + } +} diff --git a/Kaitai.Struct.Runtime/ImplicitNullable.cs b/Kaitai.Struct.Runtime/ImplicitNullable.cs new file mode 100644 index 0000000..0b8f235 --- /dev/null +++ b/Kaitai.Struct.Runtime/ImplicitNullable.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Kaitai +{ + public struct ImplicitNullable where T : struct + { + public bool HasValue { get { return this._value.HasValue; } } + public T Value { get { return this._value ?? default; } } + + public ImplicitNullable(T value) : this() { this._value = value; } + public ImplicitNullable(T? value) : this() { this._value = value; } + + public static implicit operator ImplicitNullable(T value) { return new ImplicitNullable(value); } + public static implicit operator ImplicitNullable(T? value) { return new ImplicitNullable(value); } + + public static implicit operator T(ImplicitNullable value) { return value._value ?? default(T); } + public static implicit operator T?(ImplicitNullable value) { return value._value; } + + private T? _value { get; set; } + + // Should define other Nullable members, especially + // Equals and GetHashCode to avoid boxing + } +} diff --git a/Kaitai.Struct.Runtime/Interface/CustomDecoder.cs b/Kaitai.Struct.Runtime/Interface/CustomDecoder.cs new file mode 100644 index 0000000..6afb3c9 --- /dev/null +++ b/Kaitai.Struct.Runtime/Interface/CustomDecoder.cs @@ -0,0 +1,17 @@ +namespace Kaitai +{ + /// + /// A custom decoder interface. Implementing classes can be called from + /// inside a .ksy file using `process: XXX` syntax. + /// + public interface CustomDecoder + { + /// + /// Decodes a given byte array, according to some custom algorithm + /// (specific to implementing class) and parameters given in the + /// constructor, returning another byte array. + /// + /// Source byte array. + byte[] Decode(byte[] src); + } +} diff --git a/Kaitai.Struct.Runtime/Interface/IKaitaiStream.cs b/Kaitai.Struct.Runtime/Interface/IKaitaiStream.cs new file mode 100644 index 0000000..984f5bb --- /dev/null +++ b/Kaitai.Struct.Runtime/Interface/IKaitaiStream.cs @@ -0,0 +1,166 @@ +using System; + +namespace Kaitai +{ + public interface IKaitaiStream : IKaitaiStreamBase + { + /// + /// Seek to a specific position from the beginning of the stream + /// + /// The position to seek to + void Seek(long position); + + /// + /// Read a signed byte from the stream + /// + /// + sbyte ReadS1(); + + /// + /// Read a signed short from the stream (big endian) + /// + /// + short ReadS2be(); + + /// + /// Read a signed int from the stream (big endian) + /// + /// + int ReadS4be(); + + /// + /// Read a signed long from the stream (big endian) + /// + /// + long ReadS8be(); + + /// + /// Read a signed short from the stream (little endian) + /// + /// + short ReadS2le(); + + /// + /// Read a signed int from the stream (little endian) + /// + /// + int ReadS4le(); + + /// + /// Read a signed long from the stream (little endian) + /// + /// + long ReadS8le(); + + /// + /// Read an unsigned byte from the stream + /// + /// + byte ReadU1(); + + /// + /// Read an unsigned short from the stream (big endian) + /// + /// + ushort ReadU2be(); + + /// + /// Read an unsigned int from the stream (big endian) + /// + /// + uint ReadU4be(); + + /// + /// Read an unsigned long from the stream (big endian) + /// + /// + ulong ReadU8be(); + + /// + /// Read an unsigned short from the stream (little endian) + /// + /// + ushort ReadU2le(); + + /// + /// Read an unsigned int from the stream (little endian) + /// + /// + uint ReadU4le(); + + /// + /// Read an unsigned long from the stream (little endian) + /// + /// + ulong ReadU8le(); + + /// + /// Read a single-precision floating point value from the stream (big endian) + /// + /// + float ReadF4be(); + + /// + /// Read a double-precision floating point value from the stream (big endian) + /// + /// + double ReadF8be(); + + /// + /// Read a single-precision floating point value from the stream (little endian) + /// + /// + float ReadF4le(); + + /// + /// Read a double-precision floating point value from the stream (little endian) + /// + /// + double ReadF8le(); + + [Obsolete("use ReadBitsIntBe instead")] + ulong ReadBitsInt(int n); + ulong ReadBitsIntBe(int n); + + + ulong ReadBitsIntLe(int n); + + /// + /// Read a fixed number of bytes from the stream + /// + /// The number of bytes to read + /// + byte[] ReadBytes(long count); + + /// + /// Read a fixed number of bytes from the stream + /// + /// The number of bytes to read + /// + byte[] ReadBytes(ulong count); + + /// + /// Read all the remaining bytes from the stream until the end is reached + /// + /// + byte[] ReadBytesFull(); + + /// + /// Read a terminated string from the stream + /// + /// The string terminator value + /// True to include the terminator in the returned string + /// True to consume the terminator byte before returning + /// True to throw an error when the EOS was reached before the terminator + /// + byte[] ReadBytesTerm(byte terminator, bool includeTerminator, bool consumeTerminator, bool eosError); + + /// + /// Read a specific set of bytes and assert that they are the same as an expected result + /// + /// The expected result + /// + [Obsolete("use explicit \"if\" using ByteArrayCompare method instead")] + byte[] EnsureFixedContents(byte[] expected); + } +} diff --git a/Kaitai.Struct.Runtime/Interface/IKaitaiStreamBase.cs b/Kaitai.Struct.Runtime/Interface/IKaitaiStreamBase.cs new file mode 100644 index 0000000..7693495 --- /dev/null +++ b/Kaitai.Struct.Runtime/Interface/IKaitaiStreamBase.cs @@ -0,0 +1,56 @@ +namespace Kaitai +{ + public interface IKaitaiStreamBase + { + /// + /// Check if the stream position is at the end of the stream + /// + bool IsEof { get; } + + /// + /// Get the current position in the stream + /// + long Pos { get; } + + /// + /// Get the total length of the stream (ie. file size) + /// + long Size { get; } + + void AlignToByte(); + + /// + /// Performs XOR processing with given data, XORing every byte of the input with a single value. + /// + /// The data toe process + /// The key value to XOR with + /// Processed data + byte[] ProcessXor(byte[] value, int key); + + /// + /// Performs XOR processing with given data, XORing every byte of the input with a key + /// array, repeating from the beginning of the key array if necessary + /// + /// The data toe process + /// The key array to XOR with + /// Processed data + byte[] ProcessXor(byte[] value, byte[] key); + + /// + /// Performs a circular left rotation shift for a given buffer by a given amount of bits. + /// Pass a negative amount to rotate right. + /// + /// The data to rotate + /// The number of bytes to rotate by + /// + /// + byte[] ProcessRotateLeft(byte[] data, int amount, int groupSize); + + /// + /// Inflates a deflated zlib byte stream + /// + /// The data to deflate + /// The deflated result + byte[] ProcessZlib(byte[] data); + } +} diff --git a/Kaitai.Struct.Runtime/Kaitai.Struct.Runtime.csproj b/Kaitai.Struct.Runtime/Kaitai.Struct.Runtime.csproj new file mode 100644 index 0000000..61fd5c9 --- /dev/null +++ b/Kaitai.Struct.Runtime/Kaitai.Struct.Runtime.csproj @@ -0,0 +1,37 @@ + + + + netstandard1.3;net45 + true + 1701;1702;CS1591 + {8339A750-C407-4CE8-8E31-51CB2EFD3A4B} + + + + KaitaiStruct.Runtime.CSharp + + Kaitai Project + This library implements Kaitai Struct API for C#. + Kaitai Struct Runtime + Copyright © Kaitai Project 2016-2019 + Kaitai.Struct.Runtime + Kaitai + http://kaitai.io/ + https://github.com/kaitai-io/kaitai_struct_csharp_runtime + Kaitai Struct File-Format Binary Protocols + LICENSE.txt + icon.png + + 0.8.0.0 + 0.8.0.0 + 0.8.0.0 + + + + + + + + + + diff --git a/Kaitai.Struct.Runtime/Kaitai.Struct.Runtime.csproj.DotSettings b/Kaitai.Struct.Runtime/Kaitai.Struct.Runtime.csproj.DotSettings new file mode 100644 index 0000000..d19dc3e --- /dev/null +++ b/Kaitai.Struct.Runtime/Kaitai.Struct.Runtime.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Kaitai.Struct.Runtime/Kaitai.Struct.sln.DotSettings b/Kaitai.Struct.Runtime/Kaitai.Struct.sln.DotSettings new file mode 100644 index 0000000..98f62ff --- /dev/null +++ b/Kaitai.Struct.Runtime/Kaitai.Struct.sln.DotSettings @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file diff --git a/Kaitai.Struct.Runtime/KaitaiStream.cs b/Kaitai.Struct.Runtime/KaitaiStream.cs new file mode 100644 index 0000000..08c6712 --- /dev/null +++ b/Kaitai.Struct.Runtime/KaitaiStream.cs @@ -0,0 +1,328 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Kaitai +{ + /// + /// The base Kaitai stream which exposes an API for the Kaitai Struct framework. + /// It's based off a BinaryReader, which is a little-endian reader. + /// + public partial class KaitaiStream : KaitaiStreamBase, IKaitaiStream + { + #region Constructors + private ulong Bits; + private int BitsLeft; + private BinaryReader m_binaryReader; + + protected Stream BaseStream; + + public KaitaiStream(Stream stream) + { + BaseStream = stream; + } + + /// + /// Creates a KaitaiStream backed by a file (RO) + /// + public KaitaiStream(string file) : this(File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + } + + /// + /// Creates a KaitaiStream backed by a byte buffer + /// + public KaitaiStream(byte[] bytes) : this(new MemoryStream(bytes)) + { + } + + protected BinaryReader BinaryReader + { + get => m_binaryReader ?? (BinaryReader = new BinaryReader(BaseStream)); + set => m_binaryReader = value; + } + + #endregion + + #region Stream positioning + + public override bool IsEof => BaseStream.Position >= BaseStream.Length && BitsLeft == 0; + + public void Seek(long position) => BaseStream.Seek(position, SeekOrigin.Begin); + public void Seek(ulong position) => Seek((long)position); + + public override long Pos => BaseStream.Position; + + public override long Size => BaseStream.Length; + + #endregion + + #region Integer types + + #region Signed + + public sbyte ReadS1() => BinaryReader.ReadSByte(); + + #region Big-endian + + public short ReadS2be() => BitConverter.ToInt16(ReadBytesNormalisedBigEndian(2), 0); + + public int ReadS4be() => BitConverter.ToInt32(ReadBytesNormalisedBigEndian(4), 0); + + public long ReadS8be() => BitConverter.ToInt64(ReadBytesNormalisedBigEndian(8), 0); + + #endregion + + #region Little-endian + + public short ReadS2le() => BitConverter.ToInt16(ReadBytesNormalisedLittleEndian(2), 0); + + public int ReadS4le() => BitConverter.ToInt32(ReadBytesNormalisedLittleEndian(4), 0); + + public long ReadS8le() => BitConverter.ToInt64(ReadBytesNormalisedLittleEndian(8), 0); + + #endregion + + #endregion + + #region Unsigned + + public byte ReadU1() => BinaryReader.ReadByte(); + + #region Big-endian + + public ushort ReadU2be() => BitConverter.ToUInt16(ReadBytesNormalisedBigEndian(2), 0); + + public uint ReadU4be() => BitConverter.ToUInt32(ReadBytesNormalisedBigEndian(4), 0); + + public ulong ReadU8be() => BitConverter.ToUInt64(ReadBytesNormalisedBigEndian(8), 0); + + #endregion + + #region Little-endian + + public ushort ReadU2le() => BitConverter.ToUInt16(ReadBytesNormalisedLittleEndian(2), 0); + + public uint ReadU4le() => BitConverter.ToUInt32(ReadBytesNormalisedLittleEndian(4), 0); + + public ulong ReadU8le() => BitConverter.ToUInt64(ReadBytesNormalisedLittleEndian(8), 0); + + #endregion + + #endregion + + #endregion + + #region Floating point types + + #region Big-endian + + public float ReadF4be() => BitConverter.ToSingle(ReadBytesNormalisedBigEndian(4), 0); + + public double ReadF8be() => BitConverter.ToDouble(ReadBytesNormalisedBigEndian(8), 0); + + #endregion + + #region Little-endian + + public float ReadF4le() => BitConverter.ToSingle(ReadBytesNormalisedLittleEndian(4), 0); + + public double ReadF8le() => BitConverter.ToDouble(ReadBytesNormalisedLittleEndian(8), 0); + + #endregion + + #endregion + + #region Unaligned bit values + + public override void AlignToByte() + { + Bits = 0; + BitsLeft = 0; + } + + public ulong ReadBitsIntBe(int n) + { + int bitsNeeded = n - BitsLeft; + if (bitsNeeded > 0) + { + // 1 bit => 1 byte + // 8 bits => 1 byte + // 9 bits => 2 bytes + int bytesNeeded = (bitsNeeded - 1) / 8 + 1; + byte[] buf = ReadBytes(bytesNeeded); + for (int i = 0; i < buf.Length; i++) + { + Bits <<= 8; + Bits |= buf[i]; + BitsLeft += 8; + } + } + + // raw mask with required number of 1s, starting from lowest bit + ulong mask = GetMaskOnes(n); + // shift "bits" to align the highest bits with the mask & derive reading result + int shiftBits = BitsLeft - n; + ulong res = (Bits >> shiftBits) & mask; + // clear top bits that we've just read => AND with 1s + BitsLeft -= n; + mask = GetMaskOnes(BitsLeft); + Bits &= mask; + + return res; + } + + public ulong ReadBitsInt(int n) => ReadBitsIntBe(n); + + + //Method ported from algorithm specified @ issue#155 + public ulong ReadBitsIntLe(int n) + { + int bitsNeeded = n - BitsLeft; + + if (bitsNeeded > 0) + { + // 1 bit => 1 byte + // 8 bits => 1 byte + // 9 bits => 2 bytes + int bytesNeeded = (bitsNeeded - 1) / 8 + 1; + byte[] buf = ReadBytes(bytesNeeded); + for (int i = 0; i < buf.Length; i++) + { + ulong v = (ulong)buf[i] << BitsLeft; + Bits |= v; + BitsLeft += 8; + } + } + + // raw mask with required number of 1s, starting from lowest bit + ulong mask = GetMaskOnes(n); + + // derive reading result + ulong res = Bits & mask; + + // remove bottom bits that we've just read by shifting + Bits >>= n; + BitsLeft -= n; + + return res; + } + + #endregion + + #region Byte arrays + + public byte[] ReadBytes(long count) + { + if (count < 0 || count > Int32.MaxValue) + throw new ArgumentOutOfRangeException($"requested {count} bytes, while only non-negative int32 amount of bytes possible"); + byte[] bytes = BinaryReader.ReadBytes((int)count); + if (bytes.Length < count) + throw new EndOfStreamException($"requested {count} bytes, but got only {bytes.Length} bytes"); + return bytes; + } + + public byte[] ReadBytes(ulong count) + { + if (count > Int32.MaxValue) + throw new ArgumentOutOfRangeException($"requested {count} bytes, while only non-negative int32 amount of bytes possible"); + byte[] bytes = BinaryReader.ReadBytes((int)count); + if (bytes.Length < (int)count) + throw new EndOfStreamException($"requested {count} bytes, but got only {bytes.Length} bytes"); + return bytes; + } + + /// + /// Read bytes from the stream in little endian format and convert them to the endianness of the current platform + /// + /// The number of bytes to read + /// An array of bytes that matches the endianness of the current platform + protected byte[] ReadBytesNormalisedLittleEndian(int count) + { + byte[] bytes = ReadBytes(count); + if (!IsLittleEndian) Array.Reverse(bytes); + return bytes; + } + + /// + /// Read bytes from the stream in big endian format and convert them to the endianness of the current platform + /// + /// The number of bytes to read + /// An array of bytes that matches the endianness of the current platform + protected byte[] ReadBytesNormalisedBigEndian(int count) + { + byte[] bytes = ReadBytes(count); + if (IsLittleEndian) Array.Reverse(bytes); + return bytes; + } + + public byte[] ReadBytesFull() + { + return ReadBytes(BaseStream.Length - BaseStream.Position); + } + + /// + /// Read a terminated string from the stream + /// + /// The string terminator value + /// True to include the terminator in the returned string + /// True to consume the terminator byte before returning + /// True to throw an error when the EOS was reached before the terminator + /// + public byte[] ReadBytesTerm(byte terminator, + bool includeTerminator, + bool consumeTerminator, + bool eosError) + { + List bytes = new List(); + while (true) + { + if (IsEof) + { + if (eosError) + throw new EndOfStreamException($"End of stream reached, but no terminator `{terminator}` found"); + break; + } + + byte b = BinaryReader.ReadByte(); + if (b == terminator) + { + if (includeTerminator) bytes.Add(b); + if (!consumeTerminator) Seek(Pos - 1); + break; + } + + bytes.Add(b); + } + + return bytes.ToArray(); + } + + /// + /// Read a specific set of bytes and assert that they are the same as an expected result + /// + /// The expected result + /// + public byte[] EnsureFixedContents(byte[] expected) + { + byte[] bytes = ReadBytes(expected.Length); + + if (bytes.Length != expected.Length) + { + throw new Exception($"Expected bytes: {Convert.ToBase64String(expected)} ({expected.Length} bytes), Instead got: {Convert.ToBase64String(bytes)} ({bytes.Length} bytes)"); + } + + for (int i = 0; i < bytes.Length; i++) + { + if (bytes[i] != expected[i]) + { + throw new Exception($"Expected bytes: {Convert.ToBase64String(expected)} ({expected.Length} bytes), Instead got: {Convert.ToBase64String(bytes)} ({bytes.Length} bytes)"); + } + } + + return bytes; + } + + #endregion + } +} diff --git a/Kaitai.Struct.Runtime/KaitaiStreamBase.cs b/Kaitai.Struct.Runtime/KaitaiStreamBase.cs new file mode 100644 index 0000000..91ac153 --- /dev/null +++ b/Kaitai.Struct.Runtime/KaitaiStreamBase.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; + +namespace Kaitai +{ + public abstract class KaitaiStreamBase : IKaitaiStreamBase + { + protected static readonly bool IsLittleEndian = BitConverter.IsLittleEndian; + + public abstract bool IsEof { get; } + + public abstract long Pos { get; } + + public abstract long Size { get; } + + public abstract void AlignToByte(); + + public static byte[] BytesStripRight(byte[] src, byte padByte) + { + int newLen = src.Length; + while (newLen > 0 && src[newLen - 1] == padByte) + newLen--; + + byte[] dst = new byte[newLen]; + Array.Copy(src, dst, newLen); + return dst; + } + + public static byte[] BytesTerminate(byte[] src, byte terminator, bool includeTerminator) + { + int newLen = 0; + int maxLen = src.Length; + + while (newLen < maxLen && src[newLen] != terminator) + newLen++; + + if (includeTerminator && newLen < maxLen) + newLen++; + + byte[] dst = new byte[newLen]; + Array.Copy(src, dst, newLen); + return dst; + } + + public byte[] ProcessXor(byte[] value, int key) + { + byte[] result = new byte[value.Length]; + for (int i = 0; i < value.Length; i++) + { + result[i] = (byte)(value[i] ^ key); + } + + return result; + } + + public byte[] ProcessXor(byte[] value, byte[] key) + { + int keyLen = key.Length; + byte[] result = new byte[value.Length]; + for (int i = 0, j = 0; i < value.Length; i++, j = (j + 1) % keyLen) + { + result[i] = (byte)(value[i] ^ key[j]); + } + + return result; + } + + public byte[] ProcessRotateLeft(byte[] data, int amount, int groupSize) + { + if (amount > 7 || amount < -7) + throw new ArgumentException("Rotation of more than 7 cannot be performed.", "amount"); + if (amount < 0) amount += 8; // Rotation of -2 is the same as rotation of +6 + + byte[] r = new byte[data.Length]; + switch (groupSize) + { + case 1: + for (int i = 0; i < data.Length; i++) + { + byte bits = data[i]; + // http://stackoverflow.com/a/812039 + r[i] = (byte)((bits << amount) | (bits >> (8 - amount))); + } + + break; + default: + throw new NotImplementedException($"Unable to rotate a group of {groupSize} bytes yet"); + } + + return r; + } + + public byte[] ProcessZlib(byte[] data) + { + // See RFC 1950 (https://tools.ietf.org/html/rfc1950) + // zlib adds a header to DEFLATE streams - usually 2 bytes, + // but can be 6 bytes if FDICT is set. + // There's also 4 checksum bytes at the end of the stream. + + byte zlibCmf = data[0]; + if ((zlibCmf & 0x0F) != 0x08) + throw new NotSupportedException("Only the DEFLATE algorithm is supported for zlib data."); + + const int zlibFooter = 4; + int zlibHeader = 2; + + // If the FDICT bit (0x20) is 1, then the 4-byte dictionary is included in the header, we need to skip it + byte zlibFlg = data[1]; + if ((zlibFlg & 0x20) == 0x20) zlibHeader += 4; + + using (MemoryStream ms = new MemoryStream(data, zlibHeader, data.Length - (zlibHeader + zlibFooter))) + { + using (DeflateStream ds = new DeflateStream(ms, CompressionMode.Decompress)) + { + using (MemoryStream target = new MemoryStream()) + { + ds.CopyTo(target); + return target.ToArray(); + } + } + } + } + + /// + /// Performs modulo operation between two integers. + /// + /// + /// This method is required because C# lacks a "true" modulo + /// operator, the % operator rather being the "remainder" + /// operator. We want mod operations to always be positive. + /// + /// The value to be divided + /// The value to divide by. Must be greater than zero. + /// The result of the modulo opertion. Will always be positive. + public static int Mod(int a, int b) + { + if (b <= 0) throw new ArgumentException("Divisor of mod operation must be greater than zero.", "b"); + int r = a % b; + if (r < 0) r += b; + return r; + } + + /// + /// Performs modulo operation between two integers. + /// + /// + /// This method is required because C# lacks a "true" modulo + /// operator, the % operator rather being the "remainder" + /// operator. We want mod operations to always be positive. + /// + /// The value to be divided + /// The value to divide by. Must be greater than zero. + /// The result of the modulo opertion. Will always be positive. + public static long Mod(long a, long b) + { + if (b <= 0) throw new ArgumentException("Divisor of mod operation must be greater than zero.", "b"); + long r = a % b; + if (r < 0) r += b; + return r; + } + + /// + /// Compares two byte arrays in lexicographical order. + /// + /// negative number if a is less than b, 0 if a is equal to b, positive number if a is greater than b. + /// First byte array to compare + /// Second byte array to compare. + public static int ByteArrayCompare(byte[] a, byte[] b) + { + if (a == b) + return 0; + int al = a.Length; + int bl = b.Length; + int minLen = al < bl ? al : bl; + for (int i = 0; i < minLen; i++) + { + int cmp = a[i] - b[i]; + if (cmp != 0) + return cmp; + } + + // Reached the end of at least one of the arrays + if (al == bl) + { + return 0; + } + + return al - bl; + } + + /// + /// Reverses the string, Unicode-aware. + /// + /// taken from here + public static string StringReverse(string s) + { + TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(s); + + List elements = new List(); + while (enumerator.MoveNext()) + elements.Add(enumerator.GetTextElement()); + + elements.Reverse(); + return string.Concat(elements); + } + + protected static ulong GetMaskOnes(int n) => n == 64 ? 0xffffffffffffffffUL : (1UL << n) - 1; + } +} diff --git a/Kaitai.Struct.Runtime/KaitaiStruct.cs b/Kaitai.Struct.Runtime/KaitaiStruct.cs new file mode 100644 index 0000000..9cdff29 --- /dev/null +++ b/Kaitai.Struct.Runtime/KaitaiStruct.cs @@ -0,0 +1,17 @@ +namespace Kaitai +{ + public abstract class KaitaiStruct + { + protected KaitaiStream m_io; + + protected KaitaiStruct(KaitaiStream io) + { + m_io = io; + } + + public KaitaiStream M_Io + { + get => m_io; + } + } +} diff --git a/LICENSE.txt b/Kaitai.Struct.Runtime/LICENSE.txt similarity index 100% rename from LICENSE.txt rename to Kaitai.Struct.Runtime/LICENSE.txt diff --git a/README.md b/Kaitai.Struct.Runtime/README.md similarity index 100% rename from README.md rename to Kaitai.Struct.Runtime/README.md diff --git a/icon.png b/Kaitai.Struct.Runtime/icon.png similarity index 100% rename from icon.png rename to Kaitai.Struct.Runtime/icon.png diff --git a/Kaitai.Struct.sln b/Kaitai.Struct.sln new file mode 100644 index 0000000..e9c9610 --- /dev/null +++ b/Kaitai.Struct.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29418.71 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8339A750-C407-4CE8-8E31-51CB2EFD3A4B}") = "Kaitai.Struct.Runtime", "Kaitai.Struct.Runtime\Kaitai.Struct.Runtime.csproj", "{5E981335-06A9-46DE-9BD2-312B2CD329A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kaitai.Struct.Runtime.Async", "Kaitai.Struct.Runtime.Async\Kaitai.Struct.Runtime.Async.csproj", "{33BAA91F-0777-466A-B379-65B85F8E7793}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kaitai.Struct.Runtime.Async.Tests", "Kaitai.Struct.Runtime.Async.Tests\Kaitai.Struct.Runtime.Async.Tests.csproj", "{551D67ED-04AA-49CB-8430-33B6097854A6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5E981335-06A9-46DE-9BD2-312B2CD329A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E981335-06A9-46DE-9BD2-312B2CD329A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E981335-06A9-46DE-9BD2-312B2CD329A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E981335-06A9-46DE-9BD2-312B2CD329A4}.Release|Any CPU.Build.0 = Release|Any CPU + {33BAA91F-0777-466A-B379-65B85F8E7793}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33BAA91F-0777-466A-B379-65B85F8E7793}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33BAA91F-0777-466A-B379-65B85F8E7793}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33BAA91F-0777-466A-B379-65B85F8E7793}.Release|Any CPU.Build.0 = Release|Any CPU + {551D67ED-04AA-49CB-8430-33B6097854A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {551D67ED-04AA-49CB-8430-33B6097854A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {551D67ED-04AA-49CB-8430-33B6097854A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {551D67ED-04AA-49CB-8430-33B6097854A6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {22936F10-7B00-4CED-ADCF-2F9B4DA7F101} + EndGlobalSection +EndGlobal diff --git a/Kaitai.Struct.sln.DotSettings b/Kaitai.Struct.sln.DotSettings new file mode 100644 index 0000000..2c5120e --- /dev/null +++ b/Kaitai.Struct.sln.DotSettings @@ -0,0 +1,4 @@ + + SUT + True + True \ No newline at end of file diff --git a/KaitaiStream.cs b/KaitaiStream.cs deleted file mode 100644 index a3a80d1..0000000 --- a/KaitaiStream.cs +++ /dev/null @@ -1,683 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; - -namespace Kaitai -{ - /// - /// The base Kaitai stream which exposes an API for the Kaitai Struct framework. - /// It's based off a BinaryReader, which is a little-endian reader. - /// - public partial class KaitaiStream : BinaryReader - { - #region Constructors - - public KaitaiStream(Stream stream) : base(stream) - { - } - - /// - /// Creates a KaitaiStream backed by a file (RO) - /// - public KaitaiStream(string file) : base(File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - } - - /// - ///Creates a KaitaiStream backed by a byte buffer - /// - public KaitaiStream(byte[] bytes) : base(new MemoryStream(bytes)) - { - } - - private ulong Bits = 0; - private int BitsLeft = 0; - - static readonly bool IsLittleEndian = BitConverter.IsLittleEndian; - - #endregion - - #region Stream positioning - - /// - /// Check if the stream position is at the end of the stream - /// - public bool IsEof - { - get { return BaseStream.Position >= BaseStream.Length && BitsLeft == 0; } - } - - /// - /// Seek to a specific position from the beginning of the stream - /// - /// The position to seek to - public void Seek(long position) - { - BaseStream.Seek(position, SeekOrigin.Begin); - } - - /// - /// Get the current position in the stream - /// - public long Pos - { - get { return BaseStream.Position; } - } - - /// - /// Get the total length of the stream (ie. file size) - /// - public long Size - { - get { return BaseStream.Length; } - } - - #endregion - - #region Integer types - - #region Signed - - /// - /// Read a signed byte from the stream - /// - /// - public sbyte ReadS1() - { - return ReadSByte(); - } - - #region Big-endian - - /// - /// Read a signed short from the stream (big endian) - /// - /// - public short ReadS2be() - { - return BitConverter.ToInt16(ReadBytesNormalisedBigEndian(2), 0); - } - - /// - /// Read a signed int from the stream (big endian) - /// - /// - public int ReadS4be() - { - return BitConverter.ToInt32(ReadBytesNormalisedBigEndian(4), 0); - } - - /// - /// Read a signed long from the stream (big endian) - /// - /// - public long ReadS8be() - { - return BitConverter.ToInt64(ReadBytesNormalisedBigEndian(8), 0); - } - - #endregion - - #region Little-endian - - /// - /// Read a signed short from the stream (little endian) - /// - /// - public short ReadS2le() - { - return BitConverter.ToInt16(ReadBytesNormalisedLittleEndian(2), 0); - } - - /// - /// Read a signed int from the stream (little endian) - /// - /// - public int ReadS4le() - { - return BitConverter.ToInt32(ReadBytesNormalisedLittleEndian(4), 0); - } - - /// - /// Read a signed long from the stream (little endian) - /// - /// - public long ReadS8le() - { - return BitConverter.ToInt64(ReadBytesNormalisedLittleEndian(8), 0); - } - - #endregion - - #endregion - - #region Unsigned - - /// - /// Read an unsigned byte from the stream - /// - /// - public byte ReadU1() - { - return ReadByte(); - } - - #region Big-endian - - /// - /// Read an unsigned short from the stream (big endian) - /// - /// - public ushort ReadU2be() - { - return BitConverter.ToUInt16(ReadBytesNormalisedBigEndian(2), 0); - } - - /// - /// Read an unsigned int from the stream (big endian) - /// - /// - public uint ReadU4be() - { - return BitConverter.ToUInt32(ReadBytesNormalisedBigEndian(4), 0); - } - - /// - /// Read an unsigned long from the stream (big endian) - /// - /// - public ulong ReadU8be() - { - return BitConverter.ToUInt64(ReadBytesNormalisedBigEndian(8), 0); - } - - #endregion - - #region Little-endian - - /// - /// Read an unsigned short from the stream (little endian) - /// - /// - public ushort ReadU2le() - { - return BitConverter.ToUInt16(ReadBytesNormalisedLittleEndian(2), 0); - } - - /// - /// Read an unsigned int from the stream (little endian) - /// - /// - public uint ReadU4le() - { - return BitConverter.ToUInt32(ReadBytesNormalisedLittleEndian(4), 0); - } - - /// - /// Read an unsigned long from the stream (little endian) - /// - /// - public ulong ReadU8le() - { - return BitConverter.ToUInt64(ReadBytesNormalisedLittleEndian(8), 0); - } - - #endregion - - #endregion - - #endregion - - #region Floating point types - - #region Big-endian - - /// - /// Read a single-precision floating point value from the stream (big endian) - /// - /// - public float ReadF4be() - { - return BitConverter.ToSingle(ReadBytesNormalisedBigEndian(4), 0); - } - - /// - /// Read a double-precision floating point value from the stream (big endian) - /// - /// - public double ReadF8be() - { - return BitConverter.ToDouble(ReadBytesNormalisedBigEndian(8), 0); - } - - #endregion - - #region Little-endian - - /// - /// Read a single-precision floating point value from the stream (little endian) - /// - /// - public float ReadF4le() - { - return BitConverter.ToSingle(ReadBytesNormalisedLittleEndian(4), 0); - } - - /// - /// Read a double-precision floating point value from the stream (little endian) - /// - /// - public double ReadF8le() - { - return BitConverter.ToDouble(ReadBytesNormalisedLittleEndian(8), 0); - } - - #endregion - - #endregion - - #region Unaligned bit values - - public void AlignToByte() - { - Bits = 0; - BitsLeft = 0; - } - - public ulong ReadBitsInt(int n) - { - int bitsNeeded = n - BitsLeft; - if (bitsNeeded > 0) - { - // 1 bit => 1 byte - // 8 bits => 1 byte - // 9 bits => 2 bytes - int bytesNeeded = ((bitsNeeded - 1) / 8) + 1; - byte[] buf = ReadBytes(bytesNeeded); - for (int i = 0; i < buf.Length; i++) - { - Bits <<= 8; - Bits |= buf[i]; - BitsLeft += 8; - } - } - - // raw mask with required number of 1s, starting from lowest bit - ulong mask = GetMaskOnes(n); - // shift mask to align with highest bits available in "bits" - int shiftBits = BitsLeft - n; - mask = mask << shiftBits; - // derive reading result - ulong res = (Bits & mask) >> shiftBits; - // clear top bits that we've just read => AND with 1s - BitsLeft -= n; - mask = GetMaskOnes(BitsLeft); - Bits &= mask; - - return res; - } - - //Method ported from algorithm specified @ issue#155 - public ulong ReadBitsIntLe(int n) - { - int bitsNeeded = n - BitsLeft; - - if (bitsNeeded > 0) - { - // 1 bit => 1 byte - // 8 bits => 1 byte - // 9 bits => 2 bytes - int bytesNeeded = ((bitsNeeded - 1) / 8) + 1; - byte[] buf = ReadBytes(bytesNeeded); - for (int i = 0; i < buf.Length; i++) - { - ulong v = (ulong)((ulong)buf[i] << BitsLeft); - Bits |= v; - BitsLeft += 8; - } - } - - // raw mask with required number of 1s, starting from lowest bit - ulong mask = GetMaskOnes(n); - - // derive reading result - ulong res = (Bits & mask); - - // remove bottom bits that we've just read by shifting - Bits >>= n; - BitsLeft -= n; - - return res; - } - - private static ulong GetMaskOnes(int n) - { - return n == 64 ? 0xffffffffffffffffUL : (1UL << n) - 1; - } - - #endregion - - #region Byte arrays - - /// - /// Read a fixed number of bytes from the stream - /// - /// The number of bytes to read - /// - public byte[] ReadBytes(long count) - { - if (count < 0 || count > Int32.MaxValue) - throw new ArgumentOutOfRangeException("requested " + count + " bytes, while only non-negative int32 amount of bytes possible"); - byte[] bytes = base.ReadBytes((int) count); - if (bytes.Length < count) - throw new EndOfStreamException("requested " + count + " bytes, but got only " + bytes.Length + " bytes"); - return bytes; - } - - /// - /// Read a fixed number of bytes from the stream - /// - /// The number of bytes to read - /// - public byte[] ReadBytes(ulong count) - { - if (count > Int32.MaxValue) - throw new ArgumentOutOfRangeException("requested " + count + " bytes, while only non-negative int32 amount of bytes possible"); - byte[] bytes = base.ReadBytes((int)count); - if (bytes.Length < (int)count) - throw new EndOfStreamException("requested " + count + " bytes, but got only " + bytes.Length + " bytes"); - return bytes; - } - - /// - /// Read bytes from the stream in little endian format and convert them to the endianness of the current platform - /// - /// The number of bytes to read - /// An array of bytes that matches the endianness of the current platform - protected byte[] ReadBytesNormalisedLittleEndian(int count) - { - byte[] bytes = ReadBytes(count); - if (!IsLittleEndian) Array.Reverse(bytes); - return bytes; - } - - /// - /// Read bytes from the stream in big endian format and convert them to the endianness of the current platform - /// - /// The number of bytes to read - /// An array of bytes that matches the endianness of the current platform - protected byte[] ReadBytesNormalisedBigEndian(int count) - { - byte[] bytes = ReadBytes(count); - if (IsLittleEndian) Array.Reverse(bytes); - return bytes; - } - - /// - /// Read all the remaining bytes from the stream until the end is reached - /// - /// - public byte[] ReadBytesFull() - { - return ReadBytes(BaseStream.Length - BaseStream.Position); - } - - /// - /// Read a terminated string from the stream - /// - /// The string terminator value - /// True to include the terminator in the returned string - /// True to consume the terminator byte before returning - /// True to throw an error when the EOS was reached before the terminator - /// - public byte[] ReadBytesTerm(byte terminator, bool includeTerminator, bool consumeTerminator, bool eosError) - { - List bytes = new List(); - while (true) - { - if (IsEof) - { - if (eosError) throw new EndOfStreamException(string.Format("End of stream reached, but no terminator `{0}` found", terminator)); - break; - } - - byte b = ReadByte(); - if (b == terminator) - { - if (includeTerminator) bytes.Add(b); - if (!consumeTerminator) Seek(Pos - 1); - break; - } - bytes.Add(b); - } - return bytes.ToArray(); - } - - /// - /// Read a specific set of bytes and assert that they are the same as an expected result - /// - /// The expected result - /// - public byte[] EnsureFixedContents(byte[] expected) - { - byte[] bytes = ReadBytes(expected.Length); - - if (bytes.Length != expected.Length) - { - throw new Exception(string.Format("Expected bytes: {0} ({1} bytes), Instead got: {2} ({3} bytes)", Convert.ToBase64String(expected), expected.Length, Convert.ToBase64String(bytes), bytes.Length)); - } - for (int i = 0; i < bytes.Length; i++) - { - if (bytes[i] != expected[i]) - { - throw new Exception(string.Format("Expected bytes: {0} ({1} bytes), Instead got: {2} ({3} bytes)", Convert.ToBase64String(expected), expected.Length, Convert.ToBase64String(bytes), bytes.Length)); - } - } - - return bytes; - } - - public static byte[] BytesStripRight(byte[] src, byte padByte) - { - int newLen = src.Length; - while (newLen > 0 && src[newLen - 1] == padByte) - newLen--; - - byte[] dst = new byte[newLen]; - Array.Copy(src, dst, newLen); - return dst; - } - - public static byte[] BytesTerminate(byte[] src, byte terminator, bool includeTerminator) - { - int newLen = 0; - int maxLen = src.Length; - - while (newLen < maxLen && src[newLen] != terminator) - newLen++; - - if (includeTerminator && newLen < maxLen) - newLen++; - - byte[] dst = new byte[newLen]; - Array.Copy(src, dst, newLen); - return dst; - } - - #endregion - - #region Byte array processing - - /// - /// Performs XOR processing with given data, XORing every byte of the input with a single value. - /// - /// The data toe process - /// The key value to XOR with - /// Processed data - public byte[] ProcessXor(byte[] value, int key) - { - byte[] result = new byte[value.Length]; - for (int i = 0; i < value.Length; i++) - { - result[i] = (byte)(value[i] ^ key); - } - return result; - } - - /// - /// Performs XOR processing with given data, XORing every byte of the input with a key - /// array, repeating from the beginning of the key array if necessary - /// - /// The data toe process - /// The key array to XOR with - /// Processed data - public byte[] ProcessXor(byte[] value, byte[] key) - { - int keyLen = key.Length; - byte[] result = new byte[value.Length]; - for (int i = 0, j = 0; i < value.Length; i++, j = (j + 1) % keyLen) - { - result[i] = (byte)(value[i] ^ key[j]); - } - return result; - } - - /// - /// Performs a circular left rotation shift for a given buffer by a given amount of bits. - /// Pass a negative amount to rotate right. - /// - /// The data to rotate - /// The number of bytes to rotate by - /// - /// - public byte[] ProcessRotateLeft(byte[] data, int amount, int groupSize) - { - if (amount > 7 || amount < -7) throw new ArgumentException("Rotation of more than 7 cannot be performed.", "amount"); - if (amount < 0) amount += 8; // Rotation of -2 is the same as rotation of +6 - - byte[] r = new byte[data.Length]; - switch (groupSize) - { - case 1: - for (int i = 0; i < data.Length; i++) - { - byte bits = data[i]; - // http://stackoverflow.com/a/812039 - r[i] = (byte) ((bits << amount) | (bits >> (8 - amount))); - } - break; - default: - throw new NotImplementedException(string.Format("Unable to rotate a group of {0} bytes yet", groupSize)); - } - return r; - } - - /// - /// Inflates a deflated zlib byte stream - /// - /// The data to deflate - /// The deflated result - public byte[] ProcessZlib(byte[] data) - { - // See RFC 1950 (https://tools.ietf.org/html/rfc1950) - // zlib adds a header to DEFLATE streams - usually 2 bytes, - // but can be 6 bytes if FDICT is set. - // There's also 4 checksum bytes at the end of the stream. - - byte zlibCmf = data[0]; - if ((zlibCmf & 0x0F) != 0x08) throw new NotSupportedException("Only the DEFLATE algorithm is supported for zlib data."); - - const int zlibFooter = 4; - int zlibHeader = 2; - - // If the FDICT bit (0x20) is 1, then the 4-byte dictionary is included in the header, we need to skip it - byte zlibFlg = data[1]; - if ((zlibFlg & 0x20) == 0x20) zlibHeader += 4; - - using (MemoryStream ms = new MemoryStream(data, zlibHeader, data.Length - (zlibHeader + zlibFooter))) - { - using (DeflateStream ds = new DeflateStream(ms, CompressionMode.Decompress)) - { - using (MemoryStream target = new MemoryStream()) - { - ds.CopyTo(target); - return target.ToArray(); - } - } - } - } - - #endregion - - #region Misc utility methods - - /// - /// Performs modulo operation between two integers. - /// - /// - /// This method is required because C# lacks a "true" modulo - /// operator, the % operator rather being the "remainder" - /// operator. We want mod operations to always be positive. - /// - /// The value to be divided - /// The value to divide by. Must be greater than zero. - /// The result of the modulo opertion. Will always be positive. - public static int Mod(int a, int b) - { - if (b <= 0) throw new ArgumentException("Divisor of mod operation must be greater than zero.", "b"); - int r = a % b; - if (r < 0) r += b; - return r; - } - - /// - /// Performs modulo operation between two integers. - /// - /// - /// This method is required because C# lacks a "true" modulo - /// operator, the % operator rather being the "remainder" - /// operator. We want mod operations to always be positive. - /// - /// The value to be divided - /// The value to divide by. Must be greater than zero. - /// The result of the modulo opertion. Will always be positive. - public static long Mod(long a, long b) - { - if (b <= 0) throw new ArgumentException("Divisor of mod operation must be greater than zero.", "b"); - long r = a % b; - if (r < 0) r += b; - return r; - } - - /// - /// Compares two byte arrays in lexicographical order. - /// - /// negative number if a is less than b, 0 if a is equal to b, positive number if a is greater than b. - /// First byte array to compare - /// Second byte array to compare. - public static int ByteArrayCompare(byte[] a, byte[] b) - { - if (a == b) - return 0; - int al = a.Length; - int bl = b.Length; - int minLen = al < bl ? al : bl; - for (int i = 0; i < minLen; i++) { - int cmp = a[i] - b[i]; - if (cmp != 0) - return cmp; - } - - // Reached the end of at least one of the arrays - if (al == bl) { - return 0; - } else { - return al - bl; - } - } - - #endregion - } -} diff --git a/KaitaiStruct.cs b/KaitaiStruct.cs deleted file mode 100644 index 0fe152f..0000000 --- a/KaitaiStruct.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Text; - -namespace Kaitai -{ - public abstract class KaitaiStruct - { - protected KaitaiStream m_io; - - public KaitaiStream M_Io - { - get - { - return m_io; - } - } - - public KaitaiStruct(KaitaiStream io) - { - m_io = io; - } - } - - /// - /// A custom decoder interface. Implementing classes can be called from - /// inside a .ksy file using `process: XXX` syntax. - /// - public interface CustomDecoder - { - /// - /// Decodes a given byte array, according to some custom algorithm - /// (specific to implementing class) and parameters given in the - /// constructor, returning another byte array. - /// - /// Source byte array. - byte[] Decode(byte[] src); - } - - /// - /// Common ancestor for all error originating from Kaitai Struct usage. - /// Stores KSY source path, pointing to an element supposedly guilty of - /// an error. - /// - public class KaitaiStructError : Exception { - public KaitaiStructError(string msg, string srcPath) - : base(srcPath + ": " + msg) - { - this.srcPath = srcPath; - } - - protected string srcPath; - } - - /// - /// Common ancestor for all validation failures. Stores pointer to - /// KaitaiStream IO object which was involved in an error. - /// - public class ValidationFailedError : KaitaiStructError { - public ValidationFailedError(string msg, KaitaiStream io, string srcPath) - : base("at pos " + io.Pos + ": validation failed: " + msg, srcPath) - { - this.io = io; - } - - protected KaitaiStream io; - - protected static string ByteArrayToHex(byte[] arr) { - StringBuilder sb = new StringBuilder("["); - for (int i = 0; i < arr.Length; i++) - { - if (i > 0) - { - sb.Append(' '); - } - sb.Append(string.Format("{0:X2}", arr[i])); - } - sb.Append(']'); - return sb.ToString(); - } - } - - /// - /// Signals validation failure: we required "actual" value to be equal to - /// "expected", but it turned out that it's not. - /// - public class ValidationNotEqualError : ValidationFailedError { - public ValidationNotEqualError(byte[] expected, byte[] actual, KaitaiStream io, string srcPath) - : base("not equal, expected " + ByteArrayToHex(expected) + ", but got " + ByteArrayToHex(actual), io, srcPath) - { - this.expected = expected; - this.actual = actual; - } - - public ValidationNotEqualError(Object expected, Object actual, KaitaiStream io, string srcPath) - : base("not equal, expected " + expected + ", but got " + actual, io, srcPath) - { - this.expected = expected; - this.actual = actual; - } - - protected Object expected; - protected Object actual; - } -} diff --git a/kaitai_struct_runtime_csharp.csproj b/kaitai_struct_runtime_csharp.csproj deleted file mode 100644 index 5670c7b..0000000 --- a/kaitai_struct_runtime_csharp.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - netstandard1.3;net4.5 - true - 1701;1702;CS1591 - - - - KaitaiStruct.Runtime.CSharp - - Kaitai Project - This library implements Kaitai Struct API for C#. - Kaitai Struct Runtime - Copyright © Kaitai Project 2016-2019 - Kaitai.Struct.Runtime - Kaitai - http://kaitai.io/ - https://github.com/kaitai-io/kaitai_struct_csharp_runtime - Kaitai Struct File-Format Binary Protocols - LICENSE.txt - icon.png - - 0.8.0.0 - 0.8.0.0 - 0.8.0.0 - - - - - - - - - -