diff --git a/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs
new file mode 100644
index 0000000000..1af8362a02
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Tiff
+{
+ using System;
+ using System.Buffers;
+ using System.IO;
+ using System.IO.Compression;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Class to handle cases where TIFF image data is compressed using Deflate compression.
+ ///
+ ///
+ /// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
+ ///
+ internal static class DeflateTiffCompression
+ {
+ ///
+ /// Decompresses image data into the supplied buffer.
+ ///
+ /// The to read image data from.
+ /// The number of bytes to read from the input stream.
+ /// The output buffer for uncompressed data.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decompress(Stream stream, int byteCount, byte[] buffer)
+ {
+ // Read the 'zlib' header information
+ int cmf = stream.ReadByte();
+ int flag = stream.ReadByte();
+
+ if ((cmf & 0x0f) != 8)
+ {
+ throw new Exception($"Bad compression method for ZLIB header: cmf={cmf}");
+ }
+
+ // If the 'fdict' flag is set then we should skip the next four bytes
+ bool fdict = (flag & 32) != 0;
+
+ if (fdict)
+ {
+ stream.ReadByte();
+ stream.ReadByte();
+ stream.ReadByte();
+ stream.ReadByte();
+ }
+
+ // The subsequent data is the Deflate compressed data (except for the last four bytes of checksum)
+ int headerLength = fdict ? 10 : 6;
+ SubStream subStream = new SubStream(stream, byteCount - headerLength);
+ using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress, true))
+ {
+ deflateStream.ReadFull(buffer);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
index 6f9ce8f870..6108194c41 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
@@ -19,5 +19,10 @@ namespace ImageSharp.Formats.Tiff
/// Image data is compressed using PackBits compression.
///
PackBits = 1,
+
+ ///
+ /// Image data is compressed using Deflate compression.
+ ///
+ Deflate = 2,
}
}
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index 7419f17590..e7c98cad7d 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -268,6 +268,13 @@ namespace ImageSharp.Formats
break;
}
+ case TiffCompression.Deflate:
+ case TiffCompression.OldDeflate:
+ {
+ this.CompressionType = TiffCompressionType.Deflate;
+ break;
+ }
+
default:
{
throw new NotSupportedException("The specified TIFF compression format is not supported.");
@@ -482,6 +489,9 @@ namespace ImageSharp.Formats
case TiffCompressionType.PackBits:
PackBitsTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer);
break;
+ case TiffCompressionType.Deflate:
+ DeflateTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer);
+ break;
default:
throw new InvalidOperationException();
}
diff --git a/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs b/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs
new file mode 100644
index 0000000000..3bab41edb1
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs
@@ -0,0 +1,177 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+namespace ImageSharp.Formats.Tiff
+{
+ using System;
+ using System.IO;
+
+ ///
+ /// Utility class to encapsulate a sub-portion of another .
+ ///
+ ///
+ /// Note that disposing of the does not dispose the underlying
+ /// .
+ ///
+ internal class SubStream : Stream
+ {
+ private Stream innerStream;
+ private long offset;
+ private long endOffset;
+ private long length;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The underlying to wrap.
+ /// The length of the sub-stream.
+ ///
+ /// Note that calling the sub-stream with start from the current offset of the
+ /// underlying
+ ///
+ public SubStream(Stream innerStream, long length)
+ {
+ this.innerStream = innerStream;
+ this.offset = this.innerStream.Position;
+ this.endOffset = this.offset + length;
+ this.length = length;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The underlying to wrap.
+ /// The offset of the sub-stream within the underlying .
+ /// The length of the sub-stream.
+ ///
+ /// Note that calling the constructor will immediately move the underlying
+ /// to the specified offset.
+ ///
+ public SubStream(Stream innerStream, long offset, long length)
+ {
+ this.innerStream = innerStream;
+ this.offset = offset;
+ this.endOffset = offset + length;
+ this.length = length;
+
+ innerStream.Seek(offset, SeekOrigin.Begin);
+ }
+
+ ///
+ public override bool CanRead
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ ///
+ public override bool CanWrite
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ ///
+ public override bool CanSeek
+ {
+ get
+ {
+ return this.innerStream.CanSeek;
+ }
+ }
+
+ ///
+ public override long Length
+ {
+ get
+ {
+ return this.length;
+ }
+ }
+
+ ///
+ public override long Position
+ {
+ get
+ {
+ return this.innerStream.Position - this.offset;
+ }
+
+ set
+ {
+ this.Seek(value, SeekOrigin.Begin);
+ }
+ }
+
+ ///
+ public override void Flush()
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ long bytesRemaining = this.endOffset - this.innerStream.Position;
+
+ if (bytesRemaining < count)
+ {
+ count = (int)bytesRemaining;
+ }
+
+ return this.innerStream.Read(buffer, offset, count);
+ }
+
+ ///
+ public override int ReadByte()
+ {
+ if (this.innerStream.Position < this.endOffset)
+ {
+ return this.innerStream.ReadByte();
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ ///
+ public override void Write(byte[] array, int offset, int count)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public override void WriteByte(byte value)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ switch (origin)
+ {
+ case SeekOrigin.Current:
+ return this.innerStream.Seek(offset, SeekOrigin.Current) - this.offset;
+ case SeekOrigin.Begin:
+ return this.innerStream.Seek(this.offset + offset, SeekOrigin.Begin) - this.offset;
+ case SeekOrigin.End:
+ return this.innerStream.Seek(this.endOffset - offset, SeekOrigin.Begin) - this.offset;
+ default:
+ throw new ArgumentException("Invalid seek origin.");
+ }
+ }
+
+ ///
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
index 64e3527453..59b2491054 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
@@ -34,5 +34,15 @@ namespace ImageSharp.Formats.Tiff
count -= bytesRead;
}
}
+
+ ///
+ /// Reads all bytes from the input stream into a buffer until the end of stream or the buffer is full.
+ ///
+ /// The stream to read from.
+ /// A buffer to store the retrieved data.
+ public static void ReadFull(this Stream stream, byte[] buffer)
+ {
+ ReadFull(stream, buffer, buffer.Length);
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
new file mode 100644
index 0000000000..e637008806
--- /dev/null
+++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
@@ -0,0 +1,48 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Tests
+{
+ using System.IO;
+ using Xunit;
+
+ using ImageSharp.Formats;
+ using ImageSharp.Formats.Tiff;
+
+ public class DeflateTiffCompressionTests
+ {
+ [Theory]
+ [InlineData(new byte[] { })]
+ [InlineData(new byte[] { 42 })] // One byte
+ [InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes
+ [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes
+ [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence
+ public void Decompress_ReadsData(byte[] data)
+ {
+ using (Stream stream = CreateCompressedStream(data))
+ {
+ byte[] buffer = new byte[data.Length];
+
+ DeflateTiffCompression.Decompress(stream, data.Length, buffer);
+
+ Assert.Equal(data, buffer);
+ }
+ }
+
+ private static Stream CreateCompressedStream(byte[] data)
+ {
+ Stream compressedStream = new MemoryStream();
+
+ using (Stream uncompressedStream = new MemoryStream(data),
+ deflateStream = new ZlibDeflateStream(compressedStream, 6))
+ {
+ uncompressedStream.CopyTo(deflateStream);
+ }
+
+ compressedStream.Seek(0, SeekOrigin.Begin);
+ return compressedStream;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs
index 693ddfea1a..6eef305ff2 100644
--- a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs
+++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs
@@ -125,6 +125,10 @@ namespace ImageSharp.Tests
[InlineData(true, TiffCompression.None, TiffCompressionType.None)]
[InlineData(false, TiffCompression.PackBits, TiffCompressionType.PackBits)]
[InlineData(true, TiffCompression.PackBits, TiffCompressionType.PackBits)]
+ [InlineData(false, TiffCompression.Deflate, TiffCompressionType.Deflate)]
+ [InlineData(true, TiffCompression.Deflate, TiffCompressionType.Deflate)]
+ [InlineData(false, TiffCompression.OldDeflate, TiffCompressionType.Deflate)]
+ [InlineData(true, TiffCompression.OldDeflate, TiffCompressionType.Deflate)]
public void ReadImageFormat_DeterminesCorrectCompressionImplementation(bool isLittleEndian, ushort compression, int compressionType)
{
Stream stream = CreateTiffGenIfd()
@@ -142,23 +146,19 @@ namespace ImageSharp.Tests
[InlineData(false, TiffCompression.Ccitt1D)]
[InlineData(false, TiffCompression.CcittGroup3Fax)]
[InlineData(false, TiffCompression.CcittGroup4Fax)]
- [InlineData(false, TiffCompression.Deflate)]
[InlineData(false, TiffCompression.ItuTRecT43)]
[InlineData(false, TiffCompression.ItuTRecT82)]
[InlineData(false, TiffCompression.Jpeg)]
[InlineData(false, TiffCompression.Lzw)]
- [InlineData(false, TiffCompression.OldDeflate)]
[InlineData(false, TiffCompression.OldJpeg)]
[InlineData(false, 999)]
[InlineData(true, TiffCompression.Ccitt1D)]
[InlineData(true, TiffCompression.CcittGroup3Fax)]
[InlineData(true, TiffCompression.CcittGroup4Fax)]
- [InlineData(true, TiffCompression.Deflate)]
[InlineData(true, TiffCompression.ItuTRecT43)]
[InlineData(true, TiffCompression.ItuTRecT82)]
[InlineData(true, TiffCompression.Jpeg)]
[InlineData(true, TiffCompression.Lzw)]
- [InlineData(true, TiffCompression.OldDeflate)]
[InlineData(true, TiffCompression.OldJpeg)]
[InlineData(true, 999)]
public void ReadImageFormat_ThrowsExceptionForUnsupportedCompression(bool isLittleEndian, ushort compression)
diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Utils/SubStreamTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Utils/SubStreamTests.cs
new file mode 100644
index 0000000000..b57a77c74c
--- /dev/null
+++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Utils/SubStreamTests.cs
@@ -0,0 +1,327 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Tests
+{
+ using System;
+ using System.IO;
+ using Xunit;
+
+ using ImageSharp.Formats.Tiff;
+
+ public class SubStreamTests
+ {
+ [Fact]
+ public void Constructor_PositionsStreamCorrectly_WithSpecifiedOffset()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ innerStream.Position = 2;
+
+ SubStream stream = new SubStream(innerStream, 4, 6);
+
+ Assert.Equal(0, stream.Position);
+ Assert.Equal(6, stream.Length);
+ Assert.Equal(4, innerStream.Position);
+ }
+
+ [Fact]
+ public void Constructor_PositionsStreamCorrectly_WithCurrentOffset()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ innerStream.Position = 2;
+
+ SubStream stream = new SubStream(innerStream, 6);
+
+ Assert.Equal(0, stream.Position);
+ Assert.Equal(6, stream.Length);
+ Assert.Equal(2, innerStream.Position);
+ }
+
+ [Fact]
+ public void CanRead_ReturnsTrue()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ Assert.True(stream.CanRead);
+ }
+
+ [Fact]
+ public void CanWrite_ReturnsFalse()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ Assert.False(stream.CanWrite);
+ }
+
+ [Fact]
+ public void CanSeek_ReturnsTrue()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ Assert.True(stream.CanSeek);
+ }
+
+ [Fact]
+ public void Length_ReturnsTheConstrainedLength()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ Assert.Equal(6, stream.Length);
+ }
+
+ [Fact]
+ public void Position_ReturnsZeroBeforeReading()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ Assert.Equal(0, stream.Position);
+ Assert.Equal(2, innerStream.Position);
+ }
+
+ [Fact]
+ public void Position_ReturnsPositionAfterReading()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ stream.Read(new byte[2], 0, 2);
+
+ Assert.Equal(2, stream.Position);
+ Assert.Equal(4, innerStream.Position);
+ }
+
+ [Fact]
+ public void Position_ReturnsPositionAfterReadingTwice()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ stream.Read(new byte[2], 0, 2);
+ stream.Read(new byte[2], 0, 2);
+
+ Assert.Equal(4, stream.Position);
+ Assert.Equal(6, innerStream.Position);
+ }
+
+ [Fact]
+ public void Position_SettingPropertySeeksToNewPosition()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ stream.Position = 3;
+
+ Assert.Equal(3, stream.Position);
+ Assert.Equal(5, innerStream.Position);
+ }
+
+ [Fact]
+ public void Flush_ThrowsNotSupportedException()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ Assert.Throws(() => stream.Flush());
+ }
+
+ [Fact]
+ public void Read_Reads_FromStartOfSubStream()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ byte[] buffer = new byte[3];
+ var result = stream.Read(buffer, 0, 3);
+
+ Assert.Equal(new byte[] { 3, 4, 5 }, buffer);
+ Assert.Equal(3, result);
+ }
+
+ [Theory]
+ [InlineData(2, SeekOrigin.Begin)]
+ [InlineData(1, SeekOrigin.Current)]
+ [InlineData(4, SeekOrigin.End)]
+ public void Read_Reads_FromMiddleOfSubStream(long offset, SeekOrigin origin)
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ stream.Position = 1;
+ stream.Seek(offset, origin);
+ byte[] buffer = new byte[3];
+ var result = stream.Read(buffer, 0, 3);
+
+ Assert.Equal(new byte[] { 5, 6, 7 }, buffer);
+ Assert.Equal(3, result);
+ }
+
+ [Theory]
+ [InlineData(3, SeekOrigin.Begin)]
+ [InlineData(2, SeekOrigin.Current)]
+ [InlineData(3, SeekOrigin.End)]
+ public void Read_Reads_FromEndOfSubStream(long offset, SeekOrigin origin)
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ stream.Position = 1;
+ stream.Seek(offset, origin);
+ byte[] buffer = new byte[3];
+ var result = stream.Read(buffer, 0, 3);
+
+ Assert.Equal(new byte[] { 6, 7, 8 }, buffer);
+ Assert.Equal(3, result);
+ }
+
+ [Theory]
+ [InlineData(4, SeekOrigin.Begin)]
+ [InlineData(3, SeekOrigin.Current)]
+ [InlineData(2, SeekOrigin.End)]
+ public void Read_Reads_FromBeyondEndOfSubStream(long offset, SeekOrigin origin)
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ stream.Position = 1;
+ stream.Seek(offset, origin);
+ byte[] buffer = new byte[3];
+ var result = stream.Read(buffer, 0, 3);
+
+ Assert.Equal(new byte[] { 7, 8, 0 }, buffer);
+ Assert.Equal(2, result);
+ }
+
+ [Fact]
+ public void ReadByte_Reads_FromStartOfSubStream()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ var result = stream.ReadByte();
+
+ Assert.Equal(3, result);
+ }
+
+ [Fact]
+ public void ReadByte_Reads_FromMiddleOfSubStream()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ stream.Position = 3;
+ var result = stream.ReadByte();
+
+ Assert.Equal(6, result);
+ }
+
+ [Fact]
+ public void ReadByte_Reads_FromEndOfSubStream()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ stream.Position = 5;
+ var result = stream.ReadByte();
+
+ Assert.Equal(8, result);
+ }
+
+ [Fact]
+ public void ReadByte_Reads_FromBeyondEndOfSubStream()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ stream.Position = 5;
+ stream.ReadByte();
+ var result = stream.ReadByte();
+
+ Assert.Equal(-1, result);
+ }
+
+ [Fact]
+ public void Write_ThrowsNotSupportedException()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ Assert.Throws(() => stream.Write(new byte[] { 1, 2 }, 0, 2));
+ }
+
+ [Fact]
+ public void WriteByte_ThrowsNotSupportedException()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ Assert.Throws(() => stream.WriteByte(42));
+ }
+
+ [Fact]
+ public void Seek_MovesToNewPosition_FromBegin()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ stream.Position = 1;
+ long result = stream.Seek(2, SeekOrigin.Begin);
+
+ Assert.Equal(2, result);
+ Assert.Equal(2, stream.Position);
+ Assert.Equal(4, innerStream.Position);
+ }
+
+ [Fact]
+ public void Seek_MovesToNewPosition_FromCurrent()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ stream.Position = 1;
+ long result = stream.Seek(2, SeekOrigin.Current);
+
+ Assert.Equal(3, result);
+ Assert.Equal(3, stream.Position);
+ Assert.Equal(5, innerStream.Position);
+ }
+
+ [Fact]
+ public void Seek_MovesToNewPosition_FromEnd()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ stream.Position = 1;
+ long result = stream.Seek(2, SeekOrigin.End);
+
+ Assert.Equal(4, result);
+ Assert.Equal(4, stream.Position);
+ Assert.Equal(6, innerStream.Position);
+ }
+
+ [Fact]
+ public void Seek_ThrowsException_WithInvalidOrigin()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ var e = Assert.Throws(() => stream.Seek(2, (SeekOrigin)99));
+ Assert.Equal("Invalid seek origin.", e.Message);
+ }
+
+ public void SetLength_ThrowsNotSupportedException()
+ {
+ Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ SubStream stream = new SubStream(innerStream, 2, 6);
+
+ Assert.Throws(() => stream.SetLength(5));
+ }
+ }
+}
\ No newline at end of file