Skip to content

Compression streams should seek to the end of compressed data when finished #73770

@jnix-abk

Description

@jnix-abk

Background and motivation

I am working with data that has packed objects that contain header bytes and then a ZLib blob. The DeflateStream class has an internal buffer that advances the BaseStream to fetch data. The problem is that the underlying Stream is then advanced past where the ZLib blob ends. Currently, I have to use reflection to reach down into the object model to get the number of bytes that were not consumed in the buffer so I can re-wind the BaseStream to where the ZLib data ended.
Like so:

//Get the Available Bytes in the internal buffer
//Reflection Path = deflateStream -> _inflater / inflater -> _zlibStream -> AvailIn
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var inflater = deflateStream
    .GetType()
    .GetFields(flags)
    .ToList()
    .Find(x => x.Name == "inflater" || x.Name == "_inflater");
var inflaterInstance = inflater.GetValue(deflateStream);
var zlibStream = inflaterInstance.GetType().GetField("_zlibStream", flags);
var zlibStreamInstance = zlibStream.GetValue(inflaterInstance);
var availIn = zlibStreamInstance.GetType().GetProperty("AvailIn");
var availInValue = availIn.GetValue(zlibStreamInstance);

//Rewind the BaseStream
deflateStream.BaseStream.Seek(-1 * (uint)availInValue, SeekOrigin.Current);

NEW proposal

Make compression streams to rewind the underlying stream when they reach end of the compressed data. That way, the underlying stream can be used for further reads (assuming it supports seeking).

ORIGINAL - API Proposal

Can you add

        public int AvailableInput => (int)_zlibStream.AvailIn;

below this:

public int AvailableOutput => (int)_zlibStream.AvailOut;

And then add

        internal int RemainingBufferBytes
        {
            get
            {
                if (_inflater != null)
                    return _inflater.AvailableInput;
                else
                    return -1;
            }
        }

here:

And then add

        public int RemainingBufferBytes { get { throw null; } }

below:

Finally add:

        /// <summary>Returns the number of unused Input Buffer bytes.  This can be used to rewind the BaseStream when reading from mixed-use data.</summary>
        public int RemainingBufferBytes
        {
            get
            {
                if (_deflateStream != null)
                    return _deflateStream.RemainingBufferBytes;
                else
                    return -1;
            }
        }

here:

I have added these to a local copy and built the whole thing. It works as expected.

API Usage

//Read the compressed data
ZLibStream inflater = new ZLibStream(mixedUseStream, CompressionMode.Decompress, true);
MemoryStream expandedContents = new MemoryStream();
inflater.CopyTo(expandedContents);

//Rewind the stream by the unused buffer bytes.
mixedUseStream.Seek(-1 * inflater.RemainingBufferBytes, SeekOrigin.Current);

//Close the stream
inflater.Close();

Alternative Designs

No response

Risks

There are no risks as this is simply exposing data to read that is already present.

Metadata

Metadata

Assignees

Labels

area-System.IO.CompressionenhancementProduct code improvement that does NOT require public API changes/additionsin-prThere is an active PR which will close this issue when it is merged

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions