|
//-----------------------------------------------------------------------------
//
// <copyright file="RightsManagementEncryptedStream.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved.
// </copyright>
//
// Description:
// This class implements stream subclass that is responsible for actual encryption decryption
//
// History:
// 06/03/2002: IgorBel: Initial implementation.
// 08/15/2002: LGolding: In the 07/17/2002 drop of the RM SDK, the server has
// changed from ULTNGSTN01 to TungstenTest07, and the
// activation URL has changed.
// 05/29/2003: LGolding: Ported to WCP tree.
// 06/15/2003: IgorBel: Krypton integration
// 04/26/2005: LGolding: Ported to managed wrappers around Promethium APIs.
// 03/20/2006: IgorBel: Security Code review fixes
//
//-----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic; // for List<>
using System.IO.Packaging;
using System.Security.Cryptography;
using System.Security.RightsManagement;
using MS.Internal.IO.Packaging;
using System.Windows;
using MS.Internal.WindowsBase;
namespace MS.Internal.IO.Packaging.CompoundFile
{
/// <summary>
/// This class inherits from base abstract Stream class and provides
/// RM decryption and encryption services
/// </summary>
internal class RightsManagementEncryptedStream: Stream
{
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
/// <summary>
/// See .NET Framework SDK under System.IO.Stream
/// </summary>
public override bool CanRead
{
get
{
// always return false if disposed
return (_baseStream != null) &&
_baseStream.CanRead &&
_baseStream.CanSeek &&
_cryptoProvider.CanDecrypt;
}
}
/// <summary>
/// See .NET Framework SDK under System.IO.Stream
/// </summary>
public override bool CanSeek
{
get
{
// always return false if disposed
return (_baseStream != null) &&
_baseStream.CanSeek;
}
}
/// <summary>
/// See .NET Framework SDK under System.IO.Stream
/// </summary>
public override bool CanWrite
{
get
{
// always return false if disposed
return (_baseStream != null) &&
_baseStream.CanWrite &&
_baseStream.CanRead &&
_baseStream.CanSeek &&
_cryptoProvider.CanDecrypt &&
_cryptoProvider.CanEncrypt;
}
}
/// <summary>
/// See .NET Framework SDK under System.IO.Stream
/// </summary>
public override long Length
{
get
{
CheckDisposed();
// guaranteed initialized by constructor
return _streamCachedLength;
}
}
/// <summary>
/// See .NET Framework SDK under System.IO.Stream
/// </summary>
public override long Position
{
get
{
CheckDisposed();
return _streamPosition;
}
set
{
// share logic
Seek(value, SeekOrigin.Begin);
}
}
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
/// <summary>
/// See .NET Framework SDK under System.IO.Stream
/// </summary>
public override void Flush()
{
CheckDisposed();
FlushCache();
_baseStream.Flush();
}
/// <summary>
/// See .NET Framework SDK under System.IO.Stream
/// </summary>
public override long Seek(long offset, SeekOrigin origin)
{
CheckDisposed();
long temp = 0;
checked
{
switch (origin)
{
case SeekOrigin.Begin:
{
temp = offset;
break;
}
case SeekOrigin.Current:
{
temp = _streamPosition + offset;
break;
}
case SeekOrigin.End:
{
temp = Length + offset;
break;
}
default:
{
throw new ArgumentOutOfRangeException("origin", SR.Get(SRID.SeekOriginInvalid));
}
}
}
if (temp < 0)
{
throw new ArgumentOutOfRangeException("offset", SR.Get(SRID.SeekNegative));
}
_streamPosition = temp;
return _streamPosition;
}
/// <summary>
/// See .NET Framework SDK under System.IO.Stream
/// </summary>
public override void SetLength(long newLength)
{
CheckDisposed();
if (newLength < 0)
{
throw new ArgumentOutOfRangeException("newLength", SR.Get(SRID.CannotMakeStreamLengthNegative));
}
_streamCachedLength = newLength;
// We are not caching this transaction for the following reason. The extra data that might
// be added to stream when the new length is higher than the existing length,
// although undefined (could be junk) must be consistent. Consistent
// is defined in a sense of multiple read requests performed on the same stream area.
// In order to guarantee this consistency we either have to come up with some initial value
// (0 or anything else) remember the initialized area (or multiple areas like that if get a set
// of non contiguous writes), and take it into account during all the transactions.
// Alternatively we can get some real bits allocated on the baseStream and take advantage
// of the underlying stream capability to preserve consistent content of the extra bits being
// allocated here.
FlushLength();
if (_streamPosition > Length)
{
_streamPosition = Length;
}
}
/// <summary>
/// See .NET Framework SDK under System.IO.Stream
/// </summary>
public override int Read(byte[] buffer, int offset, int count)
{
CheckDisposed();
PackagingUtilities.VerifyStreamReadArgs(this, buffer, offset, count);
int result = InternalRead(_streamPosition, buffer, offset, count);
FlushCacheIfNecessary();
checked { _streamPosition += result; }
return result;
}
/// <summary>
/// See .NET Framework SDK under System.IO.Stream
/// </summary>
public override void Write(byte[] buffer, int offset, int count)
{
CheckDisposed();
PackagingUtilities.VerifyStreamWriteArgs(this, buffer, offset, count);
_writeCache.Seek(this.Position, SeekOrigin.Begin);
_writeCache.Write(buffer, offset, count);
// we also might need to recalculate the size of the new updated stream
if (_writeCache.Length > Length)
{
// update our size accordingly
SetLength(_writeCache.Length);
}
checked { _streamPosition += count; }
FlushCacheIfNecessary();
}
//------------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
/// <summary>
/// Dispose(bool)
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
// multiple calls are fine - just ignore them
if (_baseStream != null)
{
FlushCache();
_baseStream.Close();
_readCache.Close(); // the life time of these two streams is exactly the
_writeCache.Close(); // same as the lifetime of the object itself
}
}
}
finally
{
_baseStream = null; // we better not get any calls after this
_readCache = null;
_writeCache = null;
base.Dispose(disposing);
}
}
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
internal RightsManagementEncryptedStream(
Stream baseStream,
CryptoProvider cryptoProvider)
{
Debug.Assert(baseStream != null);
Debug.Assert(cryptoProvider != null);
if (!cryptoProvider.CanDecrypt )
{
throw new ArgumentException(SR.Get(SRID.CryptoProviderCanNotDecrypt), "cryptoProvider");
}
if (!cryptoProvider.CanMergeBlocks)
{
throw new ArgumentException(SR.Get(SRID.CryptoProviderCanNotMergeBlocks), "cryptoProvider");
}
_baseStream = baseStream;
_cryptoProvider = cryptoProvider;
// Currently BitConverter is implemented as only supporting Little Endian byte order
// regardless of the machine type. We would like to make sure that this doesn't change
// as we need Little Endian byte order decoding capability on all machines in order to
// parse files that travel across different machine types.
Debug.Assert(BitConverter.IsLittleEndian);
// initialize stream length
ParseStreamLength();
}
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
/// <summary>
/// Initial update of _streamCachedLength and _streamOnDiskLength
/// </summary>
private void ParseStreamLength()
{
if (_streamCachedLength < 0)
{
// seek to the beginning of the stream
_baseStream.Seek(0, SeekOrigin.Begin);
// read the size prefix
byte[] prefixData = new byte[_prefixLengthSize];
int bytesRead = PackagingUtilities.ReliableRead
(_baseStream, prefixData, 0, prefixData.Length);
// decode length data (from the prefix)
if (bytesRead == 0)
{
// probably a new stream - just assume length is zero
_streamOnDiskLength = 0;
}
else
if (bytesRead < _prefixLengthSize)
{
// not zero and shorter than legal length == corrupt file
throw new FileFormatException(SR.Get(SRID.EncryptedDataStreamCorrupt));
}
else
{
checked
{
// This will throw on a negative value so we need not
// explicitly check for that
_streamOnDiskLength = (long)BitConverter.ToUInt64(prefixData, 0);
}
}
_streamCachedLength = _streamOnDiskLength;
}
}
private int InternalRead(long streamPosition, byte[] buffer, int offset, int count)
{
// use the explicitly passed in Position or reading in the stream
// we do not want to rely and change the real stream position
// as this function is called as a part of the Flush (which shouldn't change the stream Seek pointer)
long start = streamPosition;
// calculate how many bytes we can actually read
int realCount = count;
checked
{
if (start + count > Length)
{
// if we have been asked to read something beyond the size
// we need to truncate the size of the request
realCount = (int)(Length - start);
}
if (realCount <= 0)
{
return 0;
}
///////////////////////////////
// Check if we can satisfy this request from the cache
///////////////////////////////
// write cache first as it has the most fresh data
int cacheReadResult = ReadFromCache(_writeCache, start, realCount, buffer, offset);
if (cacheReadResult > 0)
{
return cacheReadResult;
}
///////////////////////////////
// We need to be careful about WriteCache Blocks that are ahead of the
// requested area. If we satisfy the result from read cache or from Disk
// fetching we need to make sure we do not get the data that overlaps
// Write Cache, and therefore contains stale bits (that are not updated
// based on the write cache).
// let's check how far write cache starts from the beginning of the request
///////////////////////////////
long writeCacheStartOftheNextBlock = FindOffsetOfNextAvailableBlockAfter(_writeCache, start);
//negative value indicates that no such block could be found
if (writeCacheStartOftheNextBlock >= 0)
{
Debug.Assert(writeCacheStartOftheNextBlock > start);
if (start + realCount > writeCacheStartOftheNextBlock)
{
realCount = (int)(writeCacheStartOftheNextBlock - start);
}
}
// read cache is second as it might have data that was overridden in the write cache
cacheReadResult = ReadFromCache(_readCache, start, realCount, buffer, offset);
if (cacheReadResult > 0)
{
return cacheReadResult;
}
///////////////////////////////
// We will fetch some data from the Disk into the read cache
// while fetching we need to be careful, so that
// we do not read data that might be already in the read cache (that would be a perf penalty for no reason)
///////////////////////////////
// let's check how far read cache starts from the beginning of the request
long readCacheStartOftheNextBlock = FindOffsetOfNextAvailableBlockAfter(_readCache, start);
//negative value indicates that no such block could be found
if (readCacheStartOftheNextBlock >= 0)
{
Debug.Assert(readCacheStartOftheNextBlock > start);
if (start + realCount > readCacheStartOftheNextBlock)
{
realCount = (int)(readCacheStartOftheNextBlock - start);
}
}
// Read Data from disk, decrypt it and cache it
FetchBlockIntoReadCache(start, realCount);
// at this point we must be able to satisfy request from the read cache
// important thing is we are guaranteed based on the logic above that the data we read
// will not overlap with any data that might be dirty in the write cache
cacheReadResult = ReadFromCache(_readCache, start, realCount, buffer, offset);
Debug.Assert(cacheReadResult > 0);
return cacheReadResult;
}
}
private int ReadFromCache(SparseMemoryStream cache, long start, int count, byte[] buffer, int bufferOffset)
{
#if DEBUG
// debug only check for valid parameters, as we generally expect callers to verify them
PackagingUtilities.VerifyStreamReadArgs(this, buffer, bufferOffset, count);
#endif
Debug.Assert(cache != null);
Debug.Assert(start >=0);
IList<MemoryStreamBlock> collection = cache.MemoryBlockCollection;
checked
{
// use BinarySearch to locate blocks of interest quickly
bool match; // exact match?
int index = FindIndexOfBlockAtOffset(cache, start, out match);
// if match was found, read from it
int bytesRead = 0;
if (match)
{
MemoryStreamBlock memStreamBlock = collection[index];
long overlapBlockOffset;
long overlapBlockSize;
// we have got an overlap which can be used to satisfy the read request,
// at least partially
PackagingUtilities.CalculateOverlap(memStreamBlock.Offset, memStreamBlock.Stream.Length,
start, count,
out overlapBlockOffset, out overlapBlockSize);
if (overlapBlockSize > 0)
{
// overlap must be starting at the start as we know for sure that
// memStreamBlock.Offset <= start
Debug.Assert(overlapBlockOffset == start);
memStreamBlock.Stream.Seek(overlapBlockOffset - memStreamBlock.Offset, SeekOrigin.Begin);
// we know that memStream will return as much data as we requested
// even if this logic changes we do not have to return everything
// a partially complete read is acceptable here
bytesRead = memStreamBlock.Stream.Read(buffer, bufferOffset, (int)overlapBlockSize);
}
}
return bytesRead;
}
}
/// <summary>
/// Uses BinarySearch to locate the index of the block that contains start
/// </summary>
/// <param name="cache"></param>
/// <param name="start"></param>
/// <param name="match">True if match found. If this is false, the index returned is where the item would appear if it existed.</param>
/// <returns>Index</returns>
private int FindIndexOfBlockAtOffset(SparseMemoryStream cache, long start, out bool match)
{
if (cache.MemoryBlockCollection.Count == 0)
{
match = false;
return 0;
}
// use BinarySearch to locate blocks of interest quickly
if (_comparisonBlock == null)
_comparisonBlock = new MemoryStreamBlock(null, start);
else
_comparisonBlock.Offset = start;
int index = cache.MemoryBlockCollection.BinarySearch(_comparisonBlock);
if (index < 0) // no match
{
// ~index represents the place at which the block we asked for
// would appear if it existed
index = ~index;
match = false;
}
else
{
match = true;
}
return index;
}
/// <summary>
/// Find offset of next available block after this offset
/// </summary>
/// <param name="cache">cache to inspect</param>
/// <param name="start">offset to start search from</param>
/// <returns>offset of block start if found, otherwise -1</returns>
/// <remarks>Contract: Call only when start is not inside any existing block.</remarks>
private long FindOffsetOfNextAvailableBlockAfter(SparseMemoryStream cache, long start)
{
Debug.Assert(start >= 0);
Debug.Assert(cache != null);
// Find the index where a new block with offset start would be placed
bool match;
int index = FindIndexOfBlockAtOffset(cache, start, out match);
Debug.Assert(!match, "Must only be called when there is no match");
// If we have an exact match, we return the offset of the next block, unless
// there are no more blocks - then we return -1
if (index >= (cache.MemoryBlockCollection.Count))
return -1; // no block beyond start
else
{
return cache.MemoryBlockCollection[index].Offset;
}
}
private void FetchBlockIntoReadCache(long start, int count)
{
///////////////////////////////
// Let's calculate the block information that need to be read
///////////////////////////////
long firstBlockOffset;
long blockCount;
int blockSize = _cryptoProvider.BlockSize;
// this call might potentially change blockSize and in case of the CryptoProvider supporting merging
// blocks it will become a multiple of original block size, big enough to cover the requested area
CalcBlockData(start, count, _cryptoProvider.CanMergeBlocks,
ref blockSize, // can be modified to be a multiple of the original value
out firstBlockOffset,
out blockCount);
Debug.Assert(blockCount > 0,
"RightsManagementEncryptedStream.Read Unable to process the request, calculated block count <= 0");
checked
{
///////////////////////////////
// READ CRYPTO DATA
///////////////////////////////
// try to seek to the first block
// this will take the prefix size into account
long newPosition = _baseStream.Seek(_prefixLengthSize + firstBlockOffset, SeekOrigin.Begin);
Debug.Assert( newPosition == _prefixLengthSize + firstBlockOffset,
"RightsManagementEncryptedStream.Read Unable to seek to required position");
// try to read all the required blocks into memory
int totalByteCount = (int)(blockCount * blockSize);
byte[] cryptoBuffer = new byte [totalByteCount];
int bytesRead = PackagingUtilities.ReliableRead(
_baseStream,
cryptoBuffer,
0,
totalByteCount, // we are asking for all the bytes _cryptoProvider.BlockSize
_cryptoProvider.BlockSize); // we are guaranteed to get at least that much, unless the end of stream is encountered
if (bytesRead < _cryptoProvider.BlockSize)
{
// we have found an unexpected end of stream
throw new FileFormatException(SR.Get(SRID.EncryptedDataStreamCorrupt));
}
/////////////////////////////////////////////
// DECRYPT DATA AND STORE IT IN THE READ CACHE
/////////////////////////////////////////////
//adjust block count according to the data that we were able to read
// it could be as few as cryptoProvider.BlockSize bytes or as many as totalByteCount
int readCryptoBlockSize = _cryptoProvider.BlockSize;
int readCryptoBlockCount = (int)(bytesRead/readCryptoBlockSize); // figure out how many blocks we read
Debug.Assert(readCryptoBlockCount >=1); // we must have at least 1
if (_cryptoProvider.CanMergeBlocks)
{
readCryptoBlockSize *= readCryptoBlockCount;
readCryptoBlockCount = 1;
}
byte[] cryptoTextBlock = new byte [readCryptoBlockSize];
//prepare read cache stream to accept data in the right position
_readCache.Seek(firstBlockOffset, SeekOrigin.Begin);
for (long i = 0; i < readCryptoBlockCount; i++)
{
// copy the appropriate data from the cryptoBuffer (read from disk)
// into the cryptoTextBlock for decryption
Array.Copy(cryptoBuffer, i * readCryptoBlockSize, cryptoTextBlock , 0, readCryptoBlockSize);
byte[] clearTextBlock = _cryptoProvider.Decrypt(cryptoTextBlock);
// put the results into the read cache
_readCache.Write(clearTextBlock, 0, readCryptoBlockSize);
}
}
}
private void FlushLength()
{
// update size of the physical stream according to the cached stream size value
if ((_streamCachedLength >=0) && // negative value indicates that it isn't dirty nothing to update
(_streamCachedLength != _streamOnDiskLength)) // if these 2 are not equal it is an andicator of a "dirty" date
{
_baseStream.Seek(0, SeekOrigin.Begin);
// write data into the prefix
byte[] prefixData = BitConverter.GetBytes((ulong)_streamCachedLength);
_baseStream.Write(prefixData, 0, prefixData.Length);
checked
{
// update base stream length , base stream always must have size equal to a
// multiple of block size plus +prefixLengthSize (do not truncate a half of a block at the end)
int blockSize = _cryptoProvider.BlockSize;
long physicalBaseStreamLength =
_prefixLengthSize +
GetBlockSpanCount(blockSize, 0, _streamCachedLength) * blockSize;
// NOTE: This call will not randomize or zero any data beyond the end of the stream. This is not considered
// a privacy issue because the data is encrypted. If the caller expects privacy, they should create a new stream
// and copy the data to that new stream.
_baseStream.SetLength(physicalBaseStreamLength);
_streamOnDiskLength = _streamCachedLength;
}
}
}
private static long GetBlockNo(long blockSize, long index)
{
Debug.Assert(blockSize > 1, "GetBlockNo recieved blockSize parameter value <= 1");
Debug.Assert(index >= 0 , "GetBlockNo recieved index parameter value < 0");
checked
{
return index / blockSize;
}
}
private static long GetBlockSpanCount(long blockSize, long index, long size)
{
checked
{
if (size == 0)
{
return 0;
}
else
{
return GetBlockNo(blockSize, index + size - 1) - GetBlockNo(blockSize, index) + 1;
}
}
}
private static void CalcBlockData(
long start, // offset of the first byte in the chunk of data
long size, // size of the chunk of data
bool canMergeBlocks, // controls whether we can automatically merge blocks (we can for block ciphers and not for stream ciphers)
ref int blockSize, // blockSize which is used as a base (it can be adjusted if canMergeBlocks == true)
// This index is only used for decryption/encryption as a
// counter measure against frequency analysis
// (it is not used to calculate actual offsets in the file )
out long firstBlockOffset, // byte offset of the first block that overlaps our chunk of data
out long blockCount) // total number of block required to completely cover our data
{
checked
{
long firstBlockNumber = GetBlockNo(blockSize, start);
firstBlockOffset = firstBlockNumber * blockSize;
blockCount = GetBlockSpanCount(blockSize, start, size);
if (canMergeBlocks)
{
// we need to recalculate everything as if it were a single large block
blockSize = (int)(blockSize * blockCount);
blockCount = 1;
}
}
}
private void CheckDisposed()
{
if (_baseStream == null)
{
throw new ObjectDisposedException(null, SR.Get(SRID.StreamObjectDisposed));
}
}
private void FlushCacheIfNecessary()
{
checked
{
if (_readCache.MemoryConsumption + _writeCache.MemoryConsumption > _autoFlushHighWaterMark)
{
FlushCache();
}
}
}
private void FlushCache()
{
checked
{
FlushLength();
// we know that it is a sorted list, which means we can keep track of update highWaterMark
// it will greately help in the case of small (1-2 bytes) update blocks
long updatedHighWaterMark = 0;
byte[] clearTextBuffer = null; // lazy init
foreach(MemoryStreamBlock memStreamBlock in _writeCache.MemoryBlockCollection)
{
long dirtyBlockOffset = memStreamBlock.Offset;
long dirtyBlockSize = memStreamBlock.Stream.Length;
//Adjust dirty block parameters according to the updatedHighWaterMark
// this way we can the blocks (part of the block) that have been taken care of by the previous reads
if (dirtyBlockOffset < updatedHighWaterMark)
{
dirtyBlockSize = dirtyBlockOffset + dirtyBlockSize -updatedHighWaterMark;
dirtyBlockOffset = updatedHighWaterMark;
}
// There is a chance that this was a small block that was updated in the previous loop cycle
// as a result of being in the same crypto block
if (dirtyBlockSize <= 0)
{
continue;
}
///////////////////////////////
// Let's calculate the block information that need to be read
///////////////////////////////
long firstBlockOffset;
long blockCount;
int blockSize = _cryptoProvider.BlockSize;
// this call might potentially change blockSize and in case of the CryptoProvider supporting merging
// blocks it will become a multiple of original block size, big enough to cover the requested area
CalcBlockData(dirtyBlockOffset, dirtyBlockSize, _cryptoProvider.CanMergeBlocks,
ref blockSize, // can be modified to the multiple of the original value
out firstBlockOffset,
out blockCount);
// We can use our own reading functionality to read this data into a buffer (possibly using cached data)
int totalByteCount = (int)(blockCount * blockSize);
if ((clearTextBuffer == null) || (clearTextBuffer.Length < totalByteCount))
{
// Allocate at least 4k (to improve chances of re-use on subsequent loop iterations)
// and with enough room for the current operation.
clearTextBuffer = new byte[Math.Max(0x1000, totalByteCount)];
}
int readCount = InternalReliableRead(firstBlockOffset, clearTextBuffer, 0, totalByteCount);
// if we have found an end of stream we should pre-populate the buffer suffix with some random data
// to make sure we do not always encrypt 0's
if (readCount < totalByteCount)
{
RandomFillUp(clearTextBuffer, readCount, totalByteCount - readCount);
}
// Encrypt The data
byte[] cryptoTextBuffer = _cryptoProvider.Encrypt(clearTextBuffer);
// Write the encrypted data out
_baseStream.Seek(firstBlockOffset + _prefixLengthSize, SeekOrigin.Begin);
_baseStream.Write(cryptoTextBuffer,0, totalByteCount);
updatedHighWaterMark = firstBlockOffset + totalByteCount;
}
}
_writeCache.SetLength(0);
_readCache.SetLength(0);
}
// We arte not using the standard library reliable read as we want to bypass the
// auto Flushing Logic which might result in recursive calls
private int InternalReliableRead(long streamPosition, byte[] buffer, int offset, int count)
{
Debug.Assert(streamPosition >= 0);
Debug.Assert(buffer != null);
Debug.Assert(count >= 0);
Debug.Assert(checked(offset + count <= buffer.Length));
// let's read the whole block into our buffer
int totalBytesRead = 0;
checked
{
while (totalBytesRead < count)
{
int bytesRead = InternalRead(
streamPosition + totalBytesRead,
buffer,
offset + totalBytesRead,
count - totalBytesRead);
if (bytesRead == 0)
{
break;
}
totalBytesRead += bytesRead;
}
}
return totalBytesRead;
}
private void RandomFillUp(Byte[] buffer, int offset, int count)
{
Debug.Assert(buffer != null);
Debug.Assert(buffer.Length > 0);
Debug.Assert(count >= 0);
Debug.Assert(checked(offset + count <= buffer.Length));
if (count == 0)
{
return;
}
if (_random == null)
{
_random = new Random();
}
if (_randomBuffer == null || (_randomBuffer.Length < count))
_randomBuffer = new byte[Math.Max(16, count)]; // current block size is 16
_random.NextBytes(_randomBuffer);
Array.Copy(_randomBuffer, 0, buffer, offset, count);
}
//------------------------------------------------------
//
// Private Members
//
//------------------------------------------------------
private Random _random;
private Stream _baseStream;
private long _streamCachedLength = -1; // starts as an invalid value (which force a read on the first usage)
// it potentially might contain information that need to be flushed
private long _streamOnDiskLength = -1; // starts as an invalid value (which force a read on the first usage)
// this value always matches to the real stream size
// as it is saved in the stream prefix.
private long _streamPosition; // always start at 0
private CryptoProvider _cryptoProvider;
private const int _prefixLengthSize = 8; // 8 byte original stream size prefix
private byte[] _randomBuffer; // re-usable buffer for random
private MemoryStreamBlock _comparisonBlock; // re-usable comparison block
/////////////////////////////////////////////////////////
// CACHING DATA SECTION
// The caching policy is the following:
// There are 2 Tracking memory streams, one used to cache Writes (data coming from the
// consumer of the APIs), and the other is used to cache Read (data read from the underlying storage)
// In both cases we cache clear text data.
// Both Caches are completely cleaned if we Get Flush call or
// we choose to clear the cache to reduce memory consumption. Potentially some kind of more advanced
// policy can be introduced here. (FIFO, or something based on a usage pattern activity)
////////////////////////////////////////////////////////
// MaxValues below are used in order to ensure that we do not trigger any form of Isolated Storage Backup
// This is not a goal here. We are definitely would like to keep SparseMemoryStream in the cached(in - memory) mode.
// In the context of our auto flush logic set at 16K we are pretty safe with those values…
private SparseMemoryStream _readCache = new SparseMemoryStream(Int32.MaxValue, Int64.MaxValue, false);
private SparseMemoryStream _writeCache = new SparseMemoryStream(Int32.MaxValue, Int64.MaxValue, false);
private const long _autoFlushHighWaterMark = 0x4000; // 16 K
}
}
|