File: System\IO\Compression\DeflateManaged\InputBuffer.cs
Web Access
Project: src\src\libraries\System.IO.Compression\src\System.IO.Compression.csproj (System.IO.Compression)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
 
namespace System.IO.Compression
{
    // This class can be used to read bits from an byte array quickly.
    // Normally we get bits from 'bitBuffer' field and bitsInBuffer stores
    // the number of bits available in 'BitBuffer'.
    // When we used up the bits in bitBuffer, we will try to get byte from
    // the byte array and copy the byte to appropriate position in bitBuffer.
    //
    // The byte array is not reused. We will go from 'start' to 'end'.
    // When we reach the end, most read operations will return -1,
    // which means we are running out of input.
 
    internal sealed class InputBuffer
    {
        private Memory<byte> _buffer;     // memory to store input
        private uint _bitBuffer;      // store the bits here, we can quickly shift in this buffer
        private int _bitsInBuffer;    // number of bits available in bitBuffer
 
        /// <summary>Total bits available in the input buffer.</summary>
        public int AvailableBits => _bitsInBuffer;
 
        /// <summary>Total bytes available in the input buffer.</summary>
        public int AvailableBytes => _buffer.Length + (_bitsInBuffer / 8);
 
        /// <summary>Ensure that count bits are in the bit buffer.</summary>
        /// <param name="count">Can be up to 16.</param>
        /// <returns>Returns false if input is not sufficient to make this true.</returns>
        public bool EnsureBitsAvailable(int count)
        {
            Debug.Assert(0 < count && count <= 16, "count is invalid.");
 
            // manual inlining to improve perf
            if (_bitsInBuffer < count)
            {
                if (NeedsInput())
                {
                    return false;
                }
 
                // insert a byte to bitbuffer
                _bitBuffer |= (uint)_buffer.Span[0] << _bitsInBuffer;
                _buffer = _buffer.Slice(1);
                _bitsInBuffer += 8;
 
                if (_bitsInBuffer < count)
                {
                    if (NeedsInput())
                    {
                        return false;
                    }
                    // insert a byte to bitbuffer
                    _bitBuffer |= (uint)_buffer.Span[0] << _bitsInBuffer;
                    _buffer = _buffer.Slice(1);
                    _bitsInBuffer += 8;
                }
            }
 
            return true;
        }
 
        /// <summary>
        /// This function will try to load 16 or more bits into bitBuffer.
        /// It returns whatever is contained in bitBuffer after loading.
        /// The main difference between this and GetBits is that this will
        /// never return -1. So the caller needs to check AvailableBits to
        /// see how many bits are available.
        /// </summary>
        public uint TryLoad16Bits()
        {
            if (_bitsInBuffer < 8)
            {
                if (_buffer.Length > 1)
                {
                    Span<byte> span = _buffer.Span;
                    _bitBuffer |= (uint)span[0] << _bitsInBuffer;
                    _bitBuffer |= (uint)span[1] << (_bitsInBuffer + 8);
                    _buffer = _buffer.Slice(2);
                    _bitsInBuffer += 16;
                }
                else if (_buffer.Length != 0)
                {
                    _bitBuffer |= (uint)_buffer.Span[0] << _bitsInBuffer;
                    _buffer = _buffer.Slice(1);
                    _bitsInBuffer += 8;
                }
            }
            else if (_bitsInBuffer < 16)
            {
                if (!_buffer.IsEmpty)
                {
                    _bitBuffer |= (uint)_buffer.Span[0] << _bitsInBuffer;
                    _buffer = _buffer.Slice(1);
                    _bitsInBuffer += 8;
                }
            }
 
            return _bitBuffer;
        }
 
        private static uint GetBitMask(int count) => ((uint)1 << count) - 1;
 
        /// <summary>Gets count bits from the input buffer. Returns -1 if not enough bits available.</summary>
        public int GetBits(int count)
        {
            Debug.Assert(0 < count && count <= 16, "count is invalid.");
 
            if (!EnsureBitsAvailable(count))
            {
                return -1;
            }
 
            int result = (int)(_bitBuffer & GetBitMask(count));
            _bitBuffer >>= count;
            _bitsInBuffer -= count;
            return result;
        }
 
        /// <summary>
        /// Copies bytes from input buffer to output buffer.
        /// You have to make sure, that the buffer is byte aligned. If not enough bytes are
        /// available, copies fewer bytes.
        /// </summary>
        /// <returns>Returns the number of bytes copied, 0 if no byte is available.</returns>
        public int CopyTo(Memory<byte> output)
        {
            Debug.Assert(_bitsInBuffer % 8 == 0);
 
            // Copy the bytes in bitBuffer first.
            int bytesFromBitBuffer = 0;
            while (_bitsInBuffer > 0 && !output.IsEmpty)
            {
                output.Span[0] = (byte)_bitBuffer;
                output = output.Slice(1);
                _bitBuffer >>= 8;
                _bitsInBuffer -= 8;
                bytesFromBitBuffer++;
            }
 
            if (output.IsEmpty)
            {
                return bytesFromBitBuffer;
            }
 
            int length = Math.Min(output.Length, _buffer.Length);
            _buffer.Slice(0, length).CopyTo(output);
            _buffer = _buffer.Slice(length);
            return bytesFromBitBuffer + length;
        }
 
        /// <summary>
        /// Copies length bytes from input buffer to output buffer starting at output[offset].
        /// You have to make sure, that the buffer is byte aligned. If not enough bytes are
        /// available, copies fewer bytes.
        /// </summary>
        /// <returns>Returns the number of bytes copied, 0 if no byte is available.</returns>
        public int CopyTo(byte[] output, int offset, int length)
        {
            Debug.Assert(output != null);
            Debug.Assert(offset >= 0);
            Debug.Assert(length >= 0);
            Debug.Assert(offset <= output.Length - length);
            Debug.Assert((_bitsInBuffer % 8) == 0);
 
            return CopyTo(output.AsMemory(offset, length));
        }
 
        /// <summary>
        /// Return true is all input bytes are used.
        /// This means the caller can call SetInput to add more input.
        /// </summary>
        public bool NeedsInput() => _buffer.IsEmpty;
 
        /// <summary>
        /// Set the byte buffer to be processed.
        /// All the bits remained in bitbuffer will be processed before the new bytes.
        /// We don't clone the byte buffer here since it is expensive.
        /// The caller should make sure after a buffer is passed in, that
        /// it will not be changed before calling this function again.
        /// </summary>
        public void SetInput(Memory<byte> buffer)
        {
            if (_buffer.IsEmpty)
            {
                _buffer = buffer;
            }
        }
 
        /// <summary>
        /// Set the byte array to be processed.
        /// All the bits remained in bitBuffer will be processed before the new bytes.
        /// We don't clone the byte array here since it is expensive.
        /// The caller should make sure after a buffer is passed in.
        /// It will not be changed before calling this function again.
        /// </summary>
        public void SetInput(byte[] buffer, int offset, int length)
        {
            Debug.Assert(buffer != null);
            Debug.Assert(offset >= 0);
            Debug.Assert(length >= 0);
            Debug.Assert(offset <= buffer.Length - length);
 
            SetInput(buffer.AsMemory(offset, length));
        }
 
        /// <summary>Skip n bits in the buffer.</summary>
        public void SkipBits(int n)
        {
            Debug.Assert(_bitsInBuffer >= n, "No enough bits in the buffer, Did you call EnsureBitsAvailable?");
            _bitBuffer >>= n;
            _bitsInBuffer -= n;
        }
 
        /// <summary>Skips to the next byte boundary.</summary>
        public void SkipToByteBoundary()
        {
            _bitBuffer >>= (_bitsInBuffer % 8);
            _bitsInBuffer -= (_bitsInBuffer % 8);
        }
    }
}