File: Base\MS\Internal\IO\Zip\ProgressiveCrcCalculatingStream.cs
Project: wpf\src\WindowsBase.csproj (WindowsBase)
//-----------------------------------------------------------------------------
//-------------   *** WARNING ***
//-------------    This file is part of a legally monitored development project.  
//-------------    Do not check in changes to this project.  Do not raid bugs on this
//-------------    code in the main PS database.  Do not contact the owner of this
//-------------    code directly.  Contact the legal team at ‘ZSLegal’ for assistance.
//-------------   *** WARNING ***
//-----------------------------------------------------------------------------
 
//-----------------------------------------------------------------------------
//
// <copyright file="ProgressiveCrcCalcuatingStream.cs" company="Microsoft">
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// Description:
//  This is an internal stream class that calcuates CRC values progressively
//      if possible
//
// History:
//  06/03/2005: Microsoft: Initial creation.
//
//-----------------------------------------------------------------------------
 
using System;
using System.Diagnostics;
using System.IO;
using System.Windows;               // For Exception strings - SRID
 
using MS.Internal.IO.Zip;
using MS.Internal.WindowsBase;
 
namespace MS.Internal.IO.Zip
{
    internal class ProgressiveCrcCalculatingStream:  Stream
    {
        ////////////////////////////////////
        // Stream section  
        /////////////////////////////////
        override public bool CanRead
        {
            get
            {
                return (_underlyingStream != null && _underlyingStream.CanRead);
            }
        }
 
        override public bool CanSeek
        {
            get
            {
                return (_underlyingStream != null && _underlyingStream.CanSeek);
            }
        }
 
        override public bool CanWrite
        {
            get
            {
                return (_underlyingStream != null && _underlyingStream.CanWrite);
            }
        }
 
        override public long Length
        {
            get
            {
                CheckDisposed();
 
                return  _underlyingStream.Length;
            }
        }
 
        override public long Position
        {
            get
            {
                CheckDisposed();
                
                return _underlyingStream.Position;
            }
            
            set
            {
                CheckDisposed();
 
                _underlyingStream.Position = value;
            }
        }
 
        public override void SetLength(long newLength)
        {
            CheckDisposed(); 
            
            if (newLength < 0)
            {
                throw new ArgumentOutOfRangeException("newLength");
            }
 
            if (newLength < _highWaterMark)
            {
                _highWaterMark = -1;
            }
 
            // We don't do any check if newLength == current length here
            //  this normally should result in no-op, but this will complicate
            //  the logic due to the need of caching the underlying stream length
            // Not doing this check here might result in CRC check being skipped
 
            _underlyingStream.SetLength(newLength);
            // Setting a new length is the same as write operation
            // CRC cannot be checked against the to-be-validated CRC anymore
            _validateCrcWithExpectedCrc = false;
 
            // mark the global dirty flag
            _blockManager.DirtyFlag = true;
        }
 
        override public long Seek(long offset, SeekOrigin origin)
        {
            CheckDisposed();
 
            return _underlyingStream.Seek(offset, origin);
        }        
 
        override public int Read(byte[] buffer, int offset, int count)
        {
            CheckDisposed();
 
            int readCount = 0;
 
            // We should calculate CRC accumulatively for the following conditions
            // 1. Seek is not supported by the underlying stream: this will be the case for
            //      writing stream in streaming mode
            // 2. This write request is consequtive to the highwater mark of the CRC calculation
            // 3. This write request is at 0 offset and the CRC hasn't been calculated yet
            if (!_underlyingStream.CanSeek)                   // Case #1
            {
                readCount = _underlyingStream.Read(buffer, offset, count);
                CrcCalculator.Accumulate(buffer, offset, readCount);
            }
            else
            {
                long originalPosition = _underlyingStream.Position;
 
                readCount = _underlyingStream.Read(buffer, offset, count);
 
                // This operation needs to be done after Read since read can throw an exception; in that case
                //  we want to preserve the original CRC
                if (originalPosition == 0 && _highWaterMark == -1)
                {
                    _highWaterMark = 0;
                    CrcCalculator.ClearCrc();
                }
 
                if (originalPosition == _highWaterMark)
                {
                    CrcCalculator.Accumulate(buffer, offset, readCount);
                    _highWaterMark = _underlyingStream.Position;
                }
 
                if (_validateCrcWithExpectedCrc && CanValidateCrcWithoutRead())
                {
                    if (CrcCalculator.Crc != _expectedCrc)
                    {
                        throw new FileFormatException(SR.Get(SRID.CorruptedData));
                    }
                }
            }
 
            return readCount;
        }
 
        override public void Write(byte[] buffer, int offset, int count)
        {
            CheckDisposed();
 
            // We should calculate CRC accumulatively for the following conditions
            // 1. Seek is not supported by the underlying stream: this will be the case for
            //      writing stream in streaming mode
            // 2. This write request is consequtive to the highwater mark of the CRC calculation
            // 3. This write request is at 0 offset and the CRC hasn't been calculated yet
            if (!_underlyingStream.CanSeek)                   // Case #1
            {
                _underlyingStream.Write(buffer, offset, count);
                CrcCalculator.Accumulate(buffer, offset, count);
            }
            else
            {
                long originalPosition = _underlyingStream.Position;
 
                // If we ever fail to Write below _highWaterMark, we want CRC to be recalculated in case
                //  if a caller decides to recover from the error
                if (originalPosition < _highWaterMark)
                {
                    _highWaterMark = -1;
                }
 
                _underlyingStream.Write(buffer, offset, count);
 
                if (originalPosition == 0)
                {
                    _highWaterMark = 0;
                    CrcCalculator.ClearCrc();
 
                }
 
                if (originalPosition == _highWaterMark)
                {
                    CrcCalculator.Accumulate(buffer, offset, count);
                    _highWaterMark = _underlyingStream.Position;
                }
            }
 
            // CRC cannot be checked against the to-be-validated CRC anymore
            _validateCrcWithExpectedCrc = false;
 
            // mark the global dirty flag
            _blockManager.DirtyFlag = true;
        }
 
        override public void Flush()
        {
            CheckDisposed();
 
            _underlyingStream.Flush();
        }
 
        /////////////////////////////
        // Internal Constructor
        /////////////////////////////        
        internal  ProgressiveCrcCalculatingStream(ZipIOBlockManager blockManager, Stream underlyingStream) :
            this(blockManager, underlyingStream, 0)
        {
            _validateCrcWithExpectedCrc = false;
        }
 
        internal  ProgressiveCrcCalculatingStream(ZipIOBlockManager blockManager, Stream underlyingStream, UInt32 expectedCrc) 
        {
            Debug.Assert(underlyingStream != null);
            Debug.Assert(blockManager != null);
 
            _blockManager = blockManager;
            _underlyingStream = underlyingStream;
            _validateCrcWithExpectedCrc = true;
            _expectedCrc = expectedCrc;
            _highWaterMark = -1;
        }
 
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
        /// <summary>
        /// Dispose(bool)
        /// </summary>
        /// <param name="disposing"></param>
        /// <remarks>We implement this because we want a consistent experience (essentially Flush our data) if the user chooses to 
        /// call Dispose() instead of Close().</remarks>
        protected override void Dispose(bool disposing)
        {
            try
            {
                if (disposing)
                {
                    //streams wrapping this stream shouldn't pass Dipose calls through 
                    // it is responsibility of the BlockManager or LocalFileBlock (in case of Remove) to call 
                    // this dispose as appropriate (that is the reason why Flush isn't called here)
 
                    // multiple calls are fine - just ignore them
                    // and we shouldn't be closing a stream which we do not own 
                    _underlyingStream = null;   
                }
            }
            finally
            {
                base.Dispose(disposing);
            }
        }
 
        /////////////////////////////
        // Internal Methods
        /////////////////////////////        
 
            
        // !!!!!!!!!!!!!!!!IMPORTANT !!!!!!!!!!!!!!!!
        // This method doesn't preserve the seek position of the underlying stream. 
        // It (non-preservation of the seek pointer) is mostly done for compression 
        // scenarios in which seeking back in the compressed streams will result in 
        // switching to the expensive simulation stream. This method is only called 
        // from scenarios during Flush Close of the package where position of the 
        // Compressed stream is insignificant 
        internal UInt32 CalculateCrc()
        {
            CheckDisposed();
 
            if (_underlyingStream.CanSeek)
            {
                long originalPosition = _underlyingStream.Position;
 
                if (_highWaterMark == -1)
                {
                    CrcCalculator.ClearCrc();
                    _highWaterMark = 0;
                }
 
                if (_highWaterMark < _underlyingStream.Length)
                {
                    _underlyingStream.Position = _highWaterMark;
                    CrcCalculator.CalculateStreamCrc(_underlyingStream);
                    _highWaterMark = _underlyingStream.Length;
                }
            }
 
            return CrcCalculator.Crc;
        }
 
        /////////////////////////////
        // Private Methods
        /////////////////////////////        
 
        private void CheckDisposed()
        {
            if (_underlyingStream == null)
            {
                throw new ObjectDisposedException(null, SR.Get(SRID.StreamObjectDisposed));            
            }
        }
 
        private Crc32Calculator CrcCalculator
        {
            get
            {
                if (_crcCalculator == null)
                {
                    _crcCalculator = new Crc32Calculator();
                }
                return _crcCalculator;
            }
        }
        
        private bool CanValidateCrcWithoutRead()
        {
            if (_underlyingStream.CanSeek && _highWaterMark == _underlyingStream.Length)
            {
                return true;
            }
 
            return false;
        }
 
        // this is only used to switch the dirty flag in case of Write or SetLength
        // no other communication is done with the BlockManager from this class
        private ZipIOBlockManager _blockManager;
    
        private long _highWaterMark;
        private Crc32Calculator _crcCalculator;
        private bool _validateCrcWithExpectedCrc;
        private UInt32 _expectedCrc;
 
        private Stream _underlyingStream;
    }
}