From 37feeb5863ddd866f6394aae9005bf2ce6ff1c33 Mon Sep 17 00:00:00 2001 From: Robert Hague Date: Sat, 5 Apr 2025 11:49:46 +0200 Subject: [PATCH 1/2] Add test coverage and fix for reading unseekable or unaligned streams --- .../AbstractTestDataFactory.cs | 2 +- MatFileHandler.Tests/MatFileReaderTests.cs | 189 +++++++++--------- .../PartialReadMatTestDataFactory.cs | 4 +- ...ream.cs => PartialUnseekableReadStream.cs} | 16 +- .../UnalignedMatTestDataFactory.cs | 37 ++++ MatFileHandler/MatFileReader.cs | 2 +- MatFileHandler/PositionTrackingStream.cs | 77 +++++++ 7 files changed, 226 insertions(+), 101 deletions(-) rename MatFileHandler.Tests/{PartialReadStream.cs => PartialUnseekableReadStream.cs} (81%) create mode 100644 MatFileHandler.Tests/UnalignedMatTestDataFactory.cs create mode 100644 MatFileHandler/PositionTrackingStream.cs diff --git a/MatFileHandler.Tests/AbstractTestDataFactory.cs b/MatFileHandler.Tests/AbstractTestDataFactory.cs index 082c81d..a7717d1 100755 --- a/MatFileHandler.Tests/AbstractTestDataFactory.cs +++ b/MatFileHandler.Tests/AbstractTestDataFactory.cs @@ -59,7 +59,7 @@ namespace MatFileHandler.Tests private TTestData ReadTestData(string filename) { - using (var stream = new FileStream(filename, FileMode.Open)) + using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read)) { return ReadDataFromStream(stream); } diff --git a/MatFileHandler.Tests/MatFileReaderTests.cs b/MatFileHandler.Tests/MatFileReaderTests.cs index 44e1e21..30ba7c2 100755 --- a/MatFileHandler.Tests/MatFileReaderTests.cs +++ b/MatFileHandler.Tests/MatFileReaderTests.cs @@ -18,12 +18,10 @@ namespace MatFileHandler.Tests /// /// Test reading all files in a given test set. /// - /// Name of the set. - [Theory] - [InlineData("good")] - public void TestReader(string testSet) + [Theory, MemberData(nameof(TestDataFactories))] + public void TestReader(AbstractTestDataFactory testFactory) { - foreach (var matFile in GetTests(testSet).GetAllTestData()) + foreach (var matFile in testFactory.GetAllTestData()) { Assert.NotEmpty(matFile.Variables); } @@ -32,10 +30,10 @@ namespace MatFileHandler.Tests /// /// Test reading lower and upper limits of integer data types. /// - [Fact] - public void TestLimits() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestLimits(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["limits"]; + var matFile = testFactory["limits"]; IArray array; array = matFile["int8_"].Value; CheckLimits(array as IArrayOf, CommonData.Int8Limits); @@ -66,10 +64,10 @@ namespace MatFileHandler.Tests /// /// Test writing lower and upper limits of integer-based complex data types. /// - [Fact] - public void TestComplexLimits() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestComplexLimits(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["limits_complex"]; + var matFile = testFactory["limits_complex"]; IArray array; array = matFile["int8_complex"].Value; CheckComplexLimits(array as IArrayOf>, CommonData.Int8Limits); @@ -102,10 +100,10 @@ namespace MatFileHandler.Tests /// /// Test reading an ASCII-encoded string. /// - [Fact] - public void TestAscii() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestAscii(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["ascii"]; + var matFile = testFactory["ascii"]; var arrayAscii = matFile["s"].Value as ICharArray; Assert.NotNull(arrayAscii); Assert.Equal(new[] { 1, 3 }, arrayAscii.Dimensions); @@ -116,10 +114,10 @@ namespace MatFileHandler.Tests /// /// Test reading a Unicode string. /// - [Fact] - public void TestUnicode() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestUnicode(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["unicode"]; + var matFile = testFactory["unicode"]; var arrayUnicode = matFile["s"].Value as ICharArray; Assert.NotNull(arrayUnicode); Assert.Equal(new[] { 1, 2 }, arrayUnicode.Dimensions); @@ -131,10 +129,10 @@ namespace MatFileHandler.Tests /// /// Test reading a wide Unicode string. /// - [Fact] - public void TestUnicodeWide() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestUnicodeWide(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["unicode-wide"]; + var matFile = testFactory["unicode-wide"]; var arrayUnicodeWide = matFile["s"].Value as ICharArray; Assert.NotNull(arrayUnicodeWide); Assert.Equal(new[] { 1, 2 }, arrayUnicodeWide.Dimensions); @@ -144,10 +142,10 @@ namespace MatFileHandler.Tests /// /// Test converting a structure array to a Double array. /// - [Fact] - public void TestConvertToDoubleArray() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestConvertToDoubleArray(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["struct"]; + var matFile = testFactory["struct"]; var array = matFile.Variables[0].Value; Assert.Null(array.ConvertToDoubleArray()); } @@ -156,10 +154,10 @@ namespace MatFileHandler.Tests /// Test converting a structure array to a Complex array. /// /// Should return null. - [Fact] - public void TestConvertToComplexArray() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestConvertToComplexArray(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["struct"]; + var matFile = testFactory["struct"]; var array = matFile.Variables[0].Value; Assert.Null(array.ConvertToComplexArray()); } @@ -167,10 +165,10 @@ namespace MatFileHandler.Tests /// /// Test reading an enumeration. /// - [Fact] - public void TestEnum() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestEnum(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["enum"]; + var matFile = testFactory["enum"]; var days = matFile["days"].Value; var enumeration = new EnumAdapter(days); Assert.Equal(5, enumeration.Values.Count); @@ -184,10 +182,10 @@ namespace MatFileHandler.Tests /// /// Test reading a structure array. /// - [Fact] - public void TestStruct() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestStruct(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["struct"]; + var matFile = testFactory["struct"]; var structure = matFile["struct_"].Value as IStructureArray; Assert.NotNull(structure); Assert.Equal(new[] { "x", "y" }, structure.FieldNames); @@ -238,10 +236,10 @@ namespace MatFileHandler.Tests /// /// Test reading a sparse array. /// - [Fact] - public void TestSparse() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestSparse(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["sparse"]; + var matFile = testFactory["sparse"]; var sparseArray = matFile["sparse_"].Value as ISparseArrayOf; Assert.NotNull(sparseArray); Assert.Equal(new[] { 4, 5 }, sparseArray.Dimensions); @@ -268,10 +266,10 @@ namespace MatFileHandler.Tests /// /// Test reading a logical array. /// - [Fact] - public void TestLogical() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestLogical(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["logical"]; + var matFile = testFactory["logical"]; var array = matFile["logical_"].Value; var logicalArray = array as IArrayOf; Assert.NotNull(logicalArray); @@ -286,10 +284,10 @@ namespace MatFileHandler.Tests /// /// Test reading a sparse logical array. /// - [Fact] - public void TestSparseLogical() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestSparseLogical(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["sparse_logical"]; + var matFile = testFactory["sparse_logical"]; var array = matFile["sparse_logical"].Value; var sparseArray = array as ISparseArrayOf; Assert.NotNull (sparseArray); @@ -305,10 +303,10 @@ namespace MatFileHandler.Tests /// /// Test reading a global variable. /// - [Fact] - public void TestGlobal() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestGlobal(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["global"]; + var matFile = testFactory["global"]; var variable = matFile.Variables.First(); Assert.True(variable.IsGlobal); } @@ -316,10 +314,10 @@ namespace MatFileHandler.Tests /// /// Test reading a sparse complex array. /// - [Fact] - public void TextSparseComplex() + [Theory, MemberData(nameof(TestDataFactories))] + public void TextSparseComplex(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["sparse_complex"]; + var matFile = testFactory["sparse_complex"]; var array = matFile["sparse_complex"].Value; var sparseArray = array as ISparseArrayOf; Assert.NotNull(sparseArray); @@ -332,10 +330,10 @@ namespace MatFileHandler.Tests /// /// Test reading an object. /// - [Fact] - public void TestObject() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestObject(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["object"]; + var matFile = testFactory["object"]; var obj = matFile["object_"].Value as IMatObject; Assert.NotNull(obj); Assert.Equal("Point", obj.ClassName); @@ -349,10 +347,10 @@ namespace MatFileHandler.Tests /// /// Test reading another object. /// - [Fact] - public void TestObject2() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestObject2(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["object2"]; + var matFile = testFactory["object2"]; var obj = matFile["object2"].Value as IMatObject; Assert.NotNull(obj); Assert.Equal("Point", obj.ClassName); @@ -372,10 +370,10 @@ namespace MatFileHandler.Tests /// /// Test reading a table. /// - [Fact] - public void TestTable() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestTable(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["table"]; + var matFile = testFactory["table"]; var obj = matFile["table_"].Value as IMatObject; var table = new TableAdapter(obj); Assert.Equal(3, table.NumberOfRows); @@ -393,10 +391,10 @@ namespace MatFileHandler.Tests /// /// Test reading a table with strings /// - [Fact] - public void TestTableWithStrings() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestTableWithStrings(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["table-with-strings"]; + var matFile = testFactory["table-with-strings"]; var obj = matFile["t"].Value as IMatObject; var table = new TableAdapter(obj); Assert.Equal(5, table.NumberOfRows); @@ -418,10 +416,10 @@ namespace MatFileHandler.Tests /// /// Test subobjects within objects. /// - [Fact] - public void TestSubobjects() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestSubobjects(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["pointWithSubpoints"]; + var matFile = testFactory["pointWithSubpoints"]; var p = matFile["p"].Value as IMatObject; Assert.Equal("Point", p.ClassName); var x = p["x"] as IMatObject; @@ -441,10 +439,10 @@ namespace MatFileHandler.Tests /// /// Test nested objects. /// - [Fact] - public void TestNestedObjects() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestNestedObjects(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["subsubPoint"]; + var matFile = testFactory["subsubPoint"]; var p = matFile["p"].Value as IMatObject; Assert.Equal("Point", p.ClassName); Assert.Equal(new[] { 1.0 }, p["x"].ConvertToDoubleArray()); @@ -459,10 +457,10 @@ namespace MatFileHandler.Tests /// /// Test datetime objects. /// - [Fact] - public void TestDatetime() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestDatetime(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["datetime"]; + var matFile = testFactory["datetime"]; var d = matFile["d"].Value as IMatObject; var datetime = new DatetimeAdapter(d); Assert.Equal(new[] { 1, 2 }, datetime.Dimensions); @@ -473,10 +471,10 @@ namespace MatFileHandler.Tests /// /// Another test for datetime objects. /// - [Fact] - public void TestDatetime2() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestDatetime2(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["datetime2"]; + var matFile = testFactory["datetime2"]; var d = matFile["d"].Value as IMatObject; var datetime = new DatetimeAdapter(d); Assert.Equal(new[] { 1, 1 }, datetime.Dimensions); @@ -488,10 +486,10 @@ namespace MatFileHandler.Tests /// /// Test string objects. /// - [Fact] - public void TestString() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestString(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["string"]; + var matFile = testFactory["string"]; var s = matFile["s"].Value as IMatObject; var str = new StringAdapter(s); Assert.Equal(new[] { 4, 1 }, str.Dimensions); @@ -504,10 +502,10 @@ namespace MatFileHandler.Tests /// /// Test duration objects. /// - [Fact] - public void TestDuration() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestDuration(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["duration"]; + var matFile = testFactory["duration"]; var d = matFile["d"].Value as IMatObject; var duration = new DurationAdapter(d); Assert.Equal(new[] { 1, 3 }, duration.Dimensions); @@ -519,10 +517,10 @@ namespace MatFileHandler.Tests /// /// Test unrepresentable datetime. /// - [Fact] - public void TestDatetime_Unrepresentable() + [Theory, MemberData(nameof(TestDataFactories))] + public void TestDatetime_Unrepresentable(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["datetime-unrepresentable"]; + var matFile = testFactory["datetime-unrepresentable"]; var obj = matFile["d"].Value as IMatObject; var datetime = new DatetimeAdapter(obj); var d0 = datetime[0]; @@ -532,10 +530,10 @@ namespace MatFileHandler.Tests /// /// Test 3-dimensional arrays. /// - [Fact] - public void Test_3DArrays() + [Theory, MemberData(nameof(TestDataFactories))] + public void Test_3DArrays(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["issue20.mat"]; + var matFile = testFactory["issue20.mat"]; var obj = matFile["a3d"].Value; var values = obj.ConvertToDoubleArray(); Assert.Equal(Enumerable.Range(1, 24).Select(x => (double)x).ToArray(), values); @@ -563,21 +561,34 @@ namespace MatFileHandler.Tests Assert.Equal(expected, obj.ConvertToMultidimensionalDoubleArray()); Assert.Null(obj.ConvertTo2dDoubleArray()); } - + /// /// Test four-dimensional arrays. /// - [Fact] - public void Test_4DArrays() + [Theory, MemberData(nameof(TestDataFactories))] + public void Test_4DArrays(AbstractTestDataFactory testFactory) { - var matFile = GetTests("good")["issue20.mat"]; + var matFile = testFactory["issue20.mat"]; var obj = matFile["a4d"].Value; Assert.Equal(Enumerable.Range(1, 120).Select(x => (double)x).ToArray(), obj.ConvertToDoubleArray()); Assert.Null(obj.ConvertTo2dDoubleArray()); } - private static AbstractTestDataFactory GetTests(string factoryName) => - new PartialReadMatTestDataFactory(Path.Combine(TestDirectory, factoryName)); + /// + /// Returns the factories that provide test data in various configurations. + /// + public static TheoryData> TestDataFactories + { + get + { + return new TheoryData> + { + new MatTestDataFactory(Path.Combine(TestDirectory, "good")), + new PartialReadMatTestDataFactory(Path.Combine(TestDirectory, "good")), + new UnalignedMatTestDataFactory(Path.Combine(TestDirectory, "good")), + }; + } + } private static void CheckLimits(IArrayOf array, T[] limits) where T : struct diff --git a/MatFileHandler.Tests/PartialReadMatTestDataFactory.cs b/MatFileHandler.Tests/PartialReadMatTestDataFactory.cs index f77f7f1..1c277b6 100644 --- a/MatFileHandler.Tests/PartialReadMatTestDataFactory.cs +++ b/MatFileHandler.Tests/PartialReadMatTestDataFactory.cs @@ -6,7 +6,7 @@ namespace MatFileHandler.Tests { /// /// Factory providing the parsed contents of .mat files, - /// wrapped in a . + /// wrapped in a . /// public class PartialReadMatTestDataFactory : MatTestDataFactory { @@ -26,7 +26,7 @@ namespace MatFileHandler.Tests /// Parsed contents of the file. protected override IMatFile ReadDataFromStream(Stream stream) { - using (var wrapper = new PartialReadStream(stream)) + using (var wrapper = new PartialUnseekableReadStream(stream)) { return base.ReadDataFromStream(wrapper); } diff --git a/MatFileHandler.Tests/PartialReadStream.cs b/MatFileHandler.Tests/PartialUnseekableReadStream.cs similarity index 81% rename from MatFileHandler.Tests/PartialReadStream.cs rename to MatFileHandler.Tests/PartialUnseekableReadStream.cs index b056457..ce828a0 100644 --- a/MatFileHandler.Tests/PartialReadStream.cs +++ b/MatFileHandler.Tests/PartialUnseekableReadStream.cs @@ -8,15 +8,15 @@ namespace MatFileHandler.Tests /// /// A stream which wraps another stream and only reads one byte at a time. /// - internal class PartialReadStream : Stream + internal class PartialUnseekableReadStream : Stream { private readonly Stream _baseStream; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The stream to wrap. - public PartialReadStream(Stream baseStream) + public PartialUnseekableReadStream(Stream baseStream) { _baseStream = baseStream; } @@ -25,7 +25,7 @@ namespace MatFileHandler.Tests public override bool CanRead => _baseStream.CanRead; /// - public override bool CanSeek => _baseStream.CanSeek; + public override bool CanSeek => false; /// public override bool CanWrite => false; @@ -36,8 +36,8 @@ namespace MatFileHandler.Tests /// public override long Position { - get => _baseStream.Position; - set => _baseStream.Position = value; + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); } /// @@ -55,13 +55,13 @@ namespace MatFileHandler.Tests /// public override long Seek(long offset, SeekOrigin origin) { - return _baseStream.Seek(offset, origin); + throw new NotSupportedException(); } /// public override void SetLength(long value) { - _baseStream.SetLength(value); + throw new NotSupportedException(); } /// diff --git a/MatFileHandler.Tests/UnalignedMatTestDataFactory.cs b/MatFileHandler.Tests/UnalignedMatTestDataFactory.cs new file mode 100644 index 0000000..8cf2f48 --- /dev/null +++ b/MatFileHandler.Tests/UnalignedMatTestDataFactory.cs @@ -0,0 +1,37 @@ +// Copyright 2017-2018 Alexander Luzgarev + +using System.IO; + +namespace MatFileHandler.Tests +{ + /// + /// Factory providing the parsed contents of .mat files, + /// which start at a non-8 byte-aligned offset in the stream. + /// + public class UnalignedMatTestDataFactory : MatTestDataFactory + { + /// + /// Initializes a new instance of the class. + /// + /// Directory containing test files. + public UnalignedMatTestDataFactory(string testDirectory) + : base(testDirectory) + { + } + + /// + protected override IMatFile ReadDataFromStream(Stream stream) + { + using (var ms = new MemoryStream()) + { + ms.Seek(3, SeekOrigin.Begin); + + stream.CopyTo(ms); + + ms.Seek(3, SeekOrigin.Begin); + + return base.ReadDataFromStream(ms); + } + } + } +} diff --git a/MatFileHandler/MatFileReader.cs b/MatFileHandler/MatFileReader.cs index a4e3ae6..adc29ac 100755 --- a/MatFileHandler/MatFileReader.cs +++ b/MatFileHandler/MatFileReader.cs @@ -28,7 +28,7 @@ namespace MatFileHandler /// Contents of the file. public IMatFile Read() { - using (var reader = new BinaryReader(Stream)) + using (var reader = new BinaryReader(new PositionTrackingStream(Stream))) { return Read(reader); } diff --git a/MatFileHandler/PositionTrackingStream.cs b/MatFileHandler/PositionTrackingStream.cs new file mode 100644 index 0000000..f62b931 --- /dev/null +++ b/MatFileHandler/PositionTrackingStream.cs @@ -0,0 +1,77 @@ +// Copyright 2017-2018 Alexander Luzgarev + +using System; +using System.IO; + +namespace MatFileHandler; + +/// +/// A stream which wraps another stream and tracks the number of bytes read +/// for the purpose of adjusting for padding. +/// +internal sealed class PositionTrackingStream : Stream +{ + private readonly Stream _baseStream; + private long _position; + + /// + /// Initializes a new instance of the class. + /// + /// The stream to wrap. + public PositionTrackingStream(Stream baseStream) + { + _baseStream = baseStream; + } + + /// + public override bool CanRead => _baseStream.CanRead; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => false; + + /// + public override long Length => _baseStream.Length; + + /// + public override long Position + { + get => _position; + set => throw new NotSupportedException(); + } + + /// + public override void Flush() => _baseStream.Flush(); + + /// + public override int Read(byte[] buffer, int offset, int count) + { + int bytesRead = _baseStream.Read(buffer, offset, count); + + _position += bytesRead; + + return bytesRead; + } + + /// + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + /// + public override void SetLength(long value) => throw new NotSupportedException(); + + /// + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + _baseStream.Dispose(); + } + + base.Dispose(disposing); + } +} -- 2.45.2 From e8bf3f89eef9d8675fdd76d135fef1aff3aed23f Mon Sep 17 00:00:00 2001 From: Robert Hague Date: Sat, 5 Apr 2025 11:52:04 +0200 Subject: [PATCH 2/2] Read compressed elements without loading into memory --- MatFileHandler/DataElementReader.cs | 35 ++++++++------- MatFileHandler/Substream.cs | 68 +++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 MatFileHandler/Substream.cs diff --git a/MatFileHandler/DataElementReader.cs b/MatFileHandler/DataElementReader.cs index c5e2679..9da8ab6 100755 --- a/MatFileHandler/DataElementReader.cs +++ b/MatFileHandler/DataElementReader.cs @@ -343,30 +343,33 @@ namespace MatFileHandler return new MatStructureArray(flags, dimensions, name, fields); } - private DataElement Read(Stream stream) - { - using (var reader = new BinaryReader(stream)) - { - return Read(reader); - } - } - private DataElement ReadCompressed(Tag tag, BinaryReader reader) { reader.ReadBytes(2); - var compressedData = reader.ReadBytes(tag.Length - 6); - reader.ReadBytes(4); - var resultStream = new MemoryStream(); - using (var compressedStream = new MemoryStream(compressedData)) + + DataElement element; + + using (var substream = new Substream(reader.BaseStream, tag.Length - 6)) { - using (var stream = new DeflateStream(compressedStream, CompressionMode.Decompress, leaveOpen: true)) + using (var deflateStream = new DeflateStream(substream, CompressionMode.Decompress)) + using (var bufferedStream = new BufferedStream(deflateStream)) + using (var positionTrackingStream = new PositionTrackingStream(bufferedStream)) + using (var innerReader = new BinaryReader(positionTrackingStream)) { - stream.CopyTo(resultStream); + element = Read(innerReader); + } + + if (substream.Position != substream.Length) + { + // In the pathological case that the deflate stream did not read the full + // length, then read out the rest manually (normally 1 byte). + reader.ReadBytes((int)(substream.Length - substream.Position)); } } - resultStream.Position = 0; - return Read(resultStream); + reader.ReadBytes(4); + + return element; } private DataElement ReadMatrix(Tag tag, BinaryReader reader) diff --git a/MatFileHandler/Substream.cs b/MatFileHandler/Substream.cs new file mode 100644 index 0000000..df32ef0 --- /dev/null +++ b/MatFileHandler/Substream.cs @@ -0,0 +1,68 @@ +// Copyright 2017-2018 Alexander Luzgarev + +using System; +using System.IO; + +namespace MatFileHandler +{ + /// + /// A stream which reads a finite section of another stream. + /// + internal sealed class Substream : Stream + { + private readonly Stream _baseStream; + private long _bytesRead; + + /// + /// Initializes a new instance of the class. + /// + /// The to wrap. + /// The number of bytes readable from this . + public Substream(Stream baseStream, long length) + { + _baseStream = baseStream; + Length = length; + } + + /// + public override bool CanRead => _baseStream.CanRead; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => false; + + /// + public override long Length { get; } + + /// + public override long Position + { + get => _bytesRead; + set => throw new NotSupportedException(); + } + + /// + public override void Flush() => _baseStream.Flush(); + + /// + public override int Read(byte[] buffer, int offset, int count) + { + int bytesRead = _baseStream.Read(buffer, offset, (int)Math.Min(count, Length - _bytesRead)); + + _bytesRead += bytesRead; + + return bytesRead; + } + + /// + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + /// + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + + /// + public override void SetLength(long value) => throw new NotSupportedException(); + } +} -- 2.45.2