diff --git a/Kaitai.Struct.Runtime.Async/Interface/IKaitaiAsyncStream.cs b/Kaitai.Struct.Runtime.Async/Interface/IKaitaiAsyncStream.cs
new file mode 100644
index 0000000..8000ad5
--- /dev/null
+++ b/Kaitai.Struct.Runtime.Async/Interface/IKaitaiAsyncStream.cs
@@ -0,0 +1,161 @@
+using System.Threading.Tasks;
+
+namespace Kaitai.Async
+{
+ public interface IKaitaiAsyncStream : IKaitaiStreamBase
+ {
+ ///
+ /// Seek to a specific position from the beginning of the stream
+ ///
+ /// The position to seek to
+ Task SeekAsync(long position);
+
+ ///
+ /// Read a signed byte from the stream
+ ///
+ ///
+ Task ReadS1Async();
+
+ ///
+ /// Read a signed short from the stream (big endian)
+ ///
+ ///
+ Task ReadS2beAsync();
+
+ ///
+ /// Read a signed int from the stream (big endian)
+ ///
+ ///
+ Task ReadS4beAsync();
+
+ ///
+ /// Read a signed long from the stream (big endian)
+ ///
+ ///
+ Task ReadS8beAsync();
+
+ ///
+ /// Read a signed short from the stream (little endian)
+ ///
+ ///
+ Task ReadS2leAsync();
+
+ ///
+ /// Read a signed int from the stream (little endian)
+ ///
+ ///
+ Task ReadS4leAsync();
+
+ ///
+ /// Read a signed long from the stream (little endian)
+ ///
+ ///
+ Task ReadS8leAsync();
+
+ ///
+ /// Read an unsigned byte from the stream
+ ///
+ ///
+ Task ReadU1Async();
+
+ ///
+ /// Read an unsigned short from the stream (big endian)
+ ///
+ ///
+ Task ReadU2beAsync();
+
+ ///
+ /// Read an unsigned int from the stream (big endian)
+ ///
+ ///
+ Task ReadU4beAsync();
+
+ ///
+ /// Read an unsigned long from the stream (big endian)
+ ///
+ ///
+ Task ReadU8beAsync();
+
+ ///
+ /// Read an unsigned short from the stream (little endian)
+ ///
+ ///
+ Task ReadU2leAsync();
+
+ ///
+ /// Read an unsigned int from the stream (little endian)
+ ///
+ ///
+ Task ReadU4leAsync();
+
+ ///
+ /// Read an unsigned long from the stream (little endian)
+ ///
+ ///
+ Task ReadU8leAsync();
+
+ ///
+ /// Read a single-precision floating point value from the stream (big endian)
+ ///
+ ///
+ Task ReadF4beAsync();
+
+ ///
+ /// Read a double-precision floating point value from the stream (big endian)
+ ///
+ ///
+ Task ReadF8beAsync();
+
+ ///
+ /// Read a single-precision floating point value from the stream (little endian)
+ ///
+ ///
+ Task ReadF4leAsync();
+
+ ///
+ /// Read a double-precision floating point value from the stream (little endian)
+ ///
+ ///
+ Task ReadF8leAsync();
+
+ Task ReadBitsIntAsync(int n);
+ Task ReadBitsIntLeAsync(int n);
+
+ ///
+ /// Read a fixed number of bytes from the stream
+ ///
+ /// The number of bytes to read
+ ///
+ Task ReadBytesAsync(long count);
+
+ ///
+ /// Read a fixed number of bytes from the stream
+ ///
+ /// The number of bytes to read
+ ///
+ Task ReadBytesAsync(ulong count);
+
+ ///
+ /// Read all the remaining bytes from the stream until the end is reached
+ ///
+ ///
+ Task ReadBytesFullAsync();
+
+ ///
+ /// 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);
+
+ ///
+ /// Read a specific set of bytes and assert that they are the same as an expected result
+ ///
+ /// The expected result
+ ///
+ Task EnsureFixedContentsAsync(byte[] expected);
+ }
+}
diff --git a/Kaitai.Struct.Runtime.Async/Kaitai.Struct.Runtime.Async.csproj b/Kaitai.Struct.Runtime.Async/Kaitai.Struct.Runtime.Async.csproj
index cb63190..848a9b2 100644
--- a/Kaitai.Struct.Runtime.Async/Kaitai.Struct.Runtime.Async.csproj
+++ b/Kaitai.Struct.Runtime.Async/Kaitai.Struct.Runtime.Async.csproj
@@ -1,7 +1,16 @@
-
+
netcoreapp3.1
+ Kaitai.Async
+
+
+
+
+
+
+
+
diff --git a/Kaitai.Struct.Runtime.Async/KaitaiAsyncStruct.cs b/Kaitai.Struct.Runtime.Async/KaitaiAsyncStruct.cs
new file mode 100644
index 0000000..7fb9253
--- /dev/null
+++ b/Kaitai.Struct.Runtime.Async/KaitaiAsyncStruct.cs
@@ -0,0 +1,15 @@
+namespace Kaitai.Async
+{
+ public abstract class KaitaiAsyncStruct
+ {
+ protected readonly KaitaiAsyncStream m_io;
+
+ protected KaitaiAsyncStruct(KaitaiAsyncStream kaitaiStream)
+ {
+ m_io = kaitaiStream;
+ }
+
+ // ReSharper disable once InconsistentNaming
+ public KaitaiAsyncStream M_Io => m_io;
+ }
+}
\ No newline at end of file
diff --git a/Kaitai.Struct.Runtime.Async/KaitaiStreamAsync.cs b/Kaitai.Struct.Runtime.Async/KaitaiStreamAsync.cs
new file mode 100644
index 0000000..dc4e37d
--- /dev/null
+++ b/Kaitai.Struct.Runtime.Async/KaitaiStreamAsync.cs
@@ -0,0 +1,424 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Threading.Tasks;
+using Overby.Extensions.AsyncBinaryReaderWriter;
+
+namespace Kaitai.Async
+{
+ public partial class KaitaiAsyncStream : KaitaiStreamBase, IKaitaiAsyncStream
+ {
+ protected Stream BaseStream;
+
+ private ulong _bits = 0;
+ private int _bitsLeft = 0;
+ private AsyncBinaryReader _asyncBinaryReader;
+
+ protected AsyncBinaryReader AsyncBinaryReader
+ {
+ get => _asyncBinaryReader ?? (_asyncBinaryReader = new AsyncBinaryReader(BaseStream));
+ set => _asyncBinaryReader = value;
+ }
+
+ #region Constructors
+
+ public KaitaiAsyncStream(Stream stream)
+ {
+ BaseStream = 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
+
+ ///
+ /// Check if the stream position is at the end of the stream
+ ///
+ public override bool IsEof => BaseStream.Position >= BaseStream.Length && _bitsLeft == 0;
+
+ ///
+ /// Seek to a specific position from the beginning of the stream
+ ///
+ /// The position to seek to
+ /// .NET Stream API does not support SeekAsync, therefore, consumer needs to define his own implementation.
+ public virtual Task SeekAsync(long position)
+ {
+ BaseStream.Seek(position, SeekOrigin.Begin);
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Get the current position in the stream
+ ///
+ public override long Pos => BaseStream.Position;
+
+ ///
+ /// Get the total length of the stream (ie. file size)
+ ///
+ public override long Size => BaseStream.Length;
+
+ #endregion
+
+ #region Integer types
+
+ #region Signed
+
+ ///
+ /// Read a signed byte from the stream
+ ///
+ ///
+ public async Task ReadS1Async() => await AsyncBinaryReader.ReadSByteAsync();
+
+ #region Big-endian
+
+ ///
+ /// Read a signed short from the stream (big endian)
+ ///
+ ///
+ public async Task ReadS2beAsync() => BitConverter.ToInt16(await ReadBytesNormalisedBigEndianAsync(2), 0);
+
+ ///
+ /// Read a signed int from the stream (big endian)
+ ///
+ ///
+ public async Task ReadS4beAsync() => BitConverter.ToInt32(await ReadBytesNormalisedBigEndianAsync(4), 0);
+
+ ///
+ /// Read a signed long from the stream (big endian)
+ ///
+ ///
+ public async Task ReadS8beAsync() => BitConverter.ToInt64(await ReadBytesNormalisedBigEndianAsync(8), 0);
+
+ #endregion
+
+ #region Little-endian
+
+ ///
+ /// Read a signed short from the stream (little endian)
+ ///
+ ///
+ public async Task ReadS2leAsync() => BitConverter.ToInt16(await ReadBytesNormalisedLittleEndianAsync(2), 0);
+
+ ///
+ /// Read a signed int from the stream (little endian)
+ ///
+ ///
+ public async Task ReadS4leAsync() => BitConverter.ToInt32(await ReadBytesNormalisedLittleEndianAsync(4), 0);
+
+ ///
+ /// Read a signed long from the stream (little endian)
+ ///
+ ///
+ public async Task ReadS8leAsync() => BitConverter.ToInt64(await ReadBytesNormalisedLittleEndianAsync(8), 0);
+
+ #endregion
+
+ #endregion
+
+ #region Unsigned
+
+ ///
+ /// Read an unsigned byte from the stream
+ ///
+ ///
+ public async Task ReadU1Async() => await AsyncBinaryReader.ReadByteAsync();
+
+ #region Big-endian
+
+ ///
+ /// Read an unsigned short from the stream (big endian)
+ ///
+ ///
+ public async Task ReadU2beAsync() => BitConverter.ToUInt16(await ReadBytesNormalisedBigEndianAsync(2), 0);
+
+ ///
+ /// Read an unsigned int from the stream (big endian)
+ ///
+ ///
+ public async Task ReadU4beAsync() => BitConverter.ToUInt32(await ReadBytesNormalisedBigEndianAsync(4), 0);
+
+ ///
+ /// Read an unsigned long from the stream (big endian)
+ ///
+ ///
+ public async Task ReadU8beAsync() => BitConverter.ToUInt64(await ReadBytesNormalisedBigEndianAsync(8), 0);
+
+ #endregion
+
+ #region Little-endian
+
+ ///
+ /// Read an unsigned short from the stream (little endian)
+ ///
+ ///
+ public async Task ReadU2leAsync() => BitConverter.ToUInt16(await ReadBytesNormalisedLittleEndianAsync(2), 0);
+
+ ///
+ /// Read an unsigned int from the stream (little endian)
+ ///
+ ///
+ public async Task ReadU4leAsync() => BitConverter.ToUInt32(await ReadBytesNormalisedLittleEndianAsync(4), 0);
+
+ ///
+ /// Read an unsigned long from the stream (little endian)
+ ///
+ ///
+ public async Task ReadU8leAsync() => BitConverter.ToUInt64(await ReadBytesNormalisedLittleEndianAsync(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 async Task ReadF4beAsync() => BitConverter.ToSingle(await ReadBytesNormalisedBigEndianAsync(4), 0);
+
+ ///
+ /// Read a double-precision floating point value from the stream (big endian)
+ ///
+ ///
+ public async Task ReadF8beAsync() => BitConverter.ToDouble(await ReadBytesNormalisedBigEndianAsync(8), 0);
+
+ #endregion
+
+ #region Little-endian
+
+ ///
+ /// Read a single-precision floating point value from the stream (little endian)
+ ///
+ ///
+ public async Task ReadF4leAsync() => BitConverter.ToSingle(await ReadBytesNormalisedLittleEndianAsync(4), 0);
+
+ ///
+ /// Read a double-precision floating point value from the stream (little endian)
+ ///
+ ///
+ public async Task ReadF8leAsync() => BitConverter.ToDouble(await ReadBytesNormalisedLittleEndianAsync(8), 0);
+
+ #endregion
+
+ #endregion
+
+ #region Unaligned bit values
+
+ public override void AlignToByte()
+ {
+ _bits = 0;
+ _bitsLeft = 0;
+ }
+
+ public async Task ReadBitsIntAsync(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 = await ReadBytesAsync(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 async Task ReadBitsIntLeAsync(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 = await ReadBytesAsync(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;
+ }
+
+ #endregion
+
+ #region Byte arrays
+
+ ///
+ /// Read a fixed number of bytes from the stream
+ ///
+ /// The number of bytes to read
+ ///
+ public async Task ReadBytesAsync(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 = await AsyncBinaryReader.ReadBytesAsync((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 async Task ReadBytesAsync(ulong count)
+ {
+ if (count > Int32.MaxValue)
+ throw new ArgumentOutOfRangeException("requested " + count + " bytes, while only non-negative int32 amount of bytes possible");
+ byte[] bytes = await AsyncBinaryReader.ReadBytesAsync((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 async Task ReadBytesNormalisedLittleEndianAsync(int count)
+ {
+ byte[] bytes = await ReadBytesAsync(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 async Task ReadBytesNormalisedBigEndianAsync(int count)
+ {
+ byte[] bytes = await ReadBytesAsync(count);
+ if (IsLittleEndian) Array.Reverse(bytes);
+ return bytes;
+ }
+
+ ///
+ /// Read all the remaining bytes from the stream until the end is reached
+ ///
+ ///
+ /// //TODO Handle asynchronously, BaseStream.Length is troublesome
+ public virtual async Task ReadBytesFullAsync() => await ReadBytesAsync(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 async Task ReadBytesTermAsync(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 = await AsyncBinaryReader.ReadByteAsync();
+ if (b == terminator)
+ {
+ if (includeTerminator) bytes.Add(b);
+ if (!consumeTerminator) await SeekAsync(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 async Task EnsureFixedContentsAsync(byte[] expected)
+ {
+ byte[] bytes = await ReadBytesAsync(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/Exception/ValidationFailedError.cs b/Kaitai.Struct.Runtime/Exception/ValidationFailedError.cs
index 4ac5992..373af24 100644
--- a/Kaitai.Struct.Runtime/Exception/ValidationFailedError.cs
+++ b/Kaitai.Struct.Runtime/Exception/ValidationFailedError.cs
@@ -8,9 +8,9 @@ namespace Kaitai
///
public class ValidationFailedError : KaitaiStructError
{
- protected KaitaiStream io;
+ protected IKaitaiStreamBase io;
- public ValidationFailedError(string msg, KaitaiStream io, string srcPath)
+ public ValidationFailedError(string msg, IKaitaiStreamBase io, string srcPath)
: base($"at pos {io.Pos}: validation failed: {msg}", srcPath)
{
this.io = io;
diff --git a/Kaitai.Struct.Runtime/Exception/ValidationNotEqualError.cs b/Kaitai.Struct.Runtime/Exception/ValidationNotEqualError.cs
index c7eb300..e97520e 100644
--- a/Kaitai.Struct.Runtime/Exception/ValidationNotEqualError.cs
+++ b/Kaitai.Struct.Runtime/Exception/ValidationNotEqualError.cs
@@ -12,7 +12,7 @@ public class ValidationNotEqualError : ValidationFailedError
protected Object expected;
- public ValidationNotEqualError(byte[] expected, byte[] actual, KaitaiStream io, string srcPath)
+ public ValidationNotEqualError(byte[] expected, byte[] actual, IKaitaiStreamBase io, string srcPath)
: base($"not equal, expected {ByteArrayToHex(expected)}, but got {ByteArrayToHex(actual)}", io,
srcPath)
{
@@ -20,7 +20,7 @@ public ValidationNotEqualError(byte[] expected, byte[] actual, KaitaiStream io,
this.actual = actual;
}
- public ValidationNotEqualError(Object expected, Object actual, KaitaiStream io, string srcPath)
+ public ValidationNotEqualError(Object expected, Object actual, IKaitaiStreamBase io, string srcPath)
: base($"not equal, expected {expected}, but got {actual}", io, srcPath)
{
this.expected = expected;
diff --git a/Kaitai.Struct.sln.DotSettings b/Kaitai.Struct.sln.DotSettings
new file mode 100644
index 0000000..72e13e3
--- /dev/null
+++ b/Kaitai.Struct.sln.DotSettings
@@ -0,0 +1,3 @@
+
+ True
+ True
\ No newline at end of file