private void FillCryptoOverflowWithExcess(byte[] buffer, int start, int initialCount, int extendedCount) { //If the original count asked for is not the same as the blocksize adjusted count //we need to buffer the extra bytes if (initialCount != extendedCount) { int overflowSize = extendedCount - initialCount; int readIndex = CryptoBlockOverflow.Length - overflowSize; //We should read the overflow into starting index OverflowLength - overflowSize index and not starting at 0 //This allows us to know how many and what index to starting reading at all at the same time BufferUtil.QuickUnsafeCopy(buffer, start + (extendedCount - overflowSize), CryptoBlockOverflow, readIndex, overflowSize); CryptoBlockOverflowReadIndex = readIndex; } }
public override async Task WriteAsync(byte[] bytes, int offset, int count) { if (offset < 0) { throw new ArgumentOutOfRangeException(nameof(offset)); } if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count)); } int blocksizeAdjustedCount = ConvertToBlocksizeCount(count); if (count == blocksizeAdjustedCount) { await DecoratedClient.WriteAsync(EncryptionServiceProvider.Crypt(bytes, offset, count), offset, count) .ConfigureAwait(false); } else { //Lock because we use the crypto buffer for this using (await WriteBuffer.BufferLock.LockAsync().ConfigureAwait(false)) { try { //We copy to the thread local buffer so we can use it as an extended buffer by "neededBytes" many more bytes. //So the buffer is very large but we'll tell it to write bytes.length + neededBytes. BufferUtil.QuickUnsafeCopy(bytes, offset, WriteBuffer.Buffer, 0, count); //dont copy full array, might only need less with count } catch (Exception e) { throw new InvalidOperationException($"Failed to copy bytes to crypto buffer. Bytes Length: {bytes.Length} Offset: {offset} Count: {count} BlocksizeAdjustedCount: {blocksizeAdjustedCount}", e); } EncryptionServiceProvider.Crypt(WriteBuffer.Buffer, 0, blocksizeAdjustedCount); //recurr to write the bytes with the now properly sized buffer. await DecoratedClient.WriteAsync(WriteBuffer.Buffer, 0, blocksizeAdjustedCount) .ConfigureAwait(false); } } }
/// <inheritdoc /> public override async Task WriteAsync(byte[] bytes, int offset, int count) { if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count)); } //If we have more bytes than we require buffering till //we should just write //We have to lock to prevent anything from touching the combined or buffer inbetween using (await BufferedData.BufferLock.LockAsync().ConfigureAwait(false)) using (await CombinedBuffer.BufferLock.LockAsync().ConfigureAwait(false)) { if (count > BufferedData.Buffer.Length && CurrentIndex == -1) { await DecoratedClient.WriteAsync(bytes, offset, count) .ConfigureAwait(false); } else if (count + CurrentIndex + 1 > BufferedData.Buffer.Length && CurrentIndex != -1) { //TODO: Do this somehow without copying BufferUtil.QuickUnsafeCopy(BufferedData.Buffer, 0, CombinedBuffer.Buffer, 0, CurrentIndex + 1); BufferUtil.QuickUnsafeCopy(bytes, offset, CombinedBuffer.Buffer, CurrentIndex + 1, count); await DecoratedClient.WriteAsync(CombinedBuffer.Buffer, 0, count + CurrentIndex + 1) .ConfigureAwait(false); CurrentIndex = -1; } else { //At this point we know that the buffer isn't large enough to write so we need to buffer it BufferUtil.QuickUnsafeCopy(bytes, offset, BufferedData.Buffer, CurrentIndex + 1, count); CurrentIndex += count; } } }
//TODO: Refactor this /// <summary> /// Reads asyncronously <see cref="count"/> many bytes from the reader. /// </summary> /// <param name="buffer">The buffer to store the bytes into.</param> /// <param name="start">The start position in the buffer to start reading into.</param> /// <param name="count">How many bytes to read.</param> /// <param name="token">The cancel token to check during the async operation.</param> /// <returns>A future for the read bytes.</returns> public override async Task <int> ReadAsync(byte[] buffer, int start, int count, CancellationToken token) { if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } if (start < 0) { throw new ArgumentOutOfRangeException(nameof(start)); } if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count), $"Cannot read less than 0 bytes. Can't read: {count} many bytes"); } //We read this outside of the lock to reduce locking time //If the above caller requested an invalid count of bytes to read //We should try to correct for it and read afew more bytes. int extendedCount = ConvertToBlocksizeCount(count); if (token.IsCancellationRequested) { return(0); } //TODO: Optimize for when the buffer is large enough. Right now it does needless BlockCopy even if there is room in the buffer //We should lock incase there are multiple calls using (await ReadBuffer.BufferLock.LockAsync(token).ConfigureAwait(false)) { int cryptoOverflowSize = CryptoBlockOverflow.Length - CryptoBlockOverflowReadIndex; //If the overflow size is the exact size asked for then //we don't need to do anything but copy the buffer and return if (cryptoOverflowSize >= count) { //Read from the crypto overflow and move the read index forward BufferUtil.QuickUnsafeCopy(CryptoBlockOverflow, CryptoBlockOverflowReadIndex, buffer, start, count); CryptoBlockOverflowReadIndex = CryptoBlockOverflowReadIndex + count; return(count); } else if (cryptoOverflowSize == 0) { //Since the buffer MAY be large enough we check to avoid a potential BlockCopy if (buffer.Length > extendedCount) { //Since the provider may not give a buffer large enough we should use the crypto buffer bool result = await ReadAndDecrypt(buffer, start, token, extendedCount) .ConfigureAwait(false); //if the read/decrypt failed then return 0 if (!result) { return(0); } FillCryptoOverflowWithExcess(buffer, 0, count, extendedCount); } else { //Since the provider may not give a buffer large enough we should use the crypto buffer bool result = await ReadAndDecrypt(ReadBuffer.Buffer, 0, token, extendedCount) .ConfigureAwait(false); //if the read/decrypt failed then return 0 if (!result) { return(0); } //Now we must BlockCopy the requested amount into the buffer BufferUtil.QuickUnsafeCopy(ReadBuffer.Buffer, 0, buffer, start, count); FillCryptoOverflowWithExcess(ReadBuffer.Buffer, 0, count, extendedCount); } } else if (cryptoOverflowSize < count) { BufferUtil.QuickUnsafeCopy(CryptoBlockOverflow, CryptoBlockOverflowReadIndex, buffer, start, cryptoOverflowSize); CryptoBlockOverflowReadIndex = CryptoBlockOverflow.Length; //set it to last index so we know we have no overflow anymore //Recompute the extended count, since we read some of the cryptooverflow it will have changed potentially int newCount = count - cryptoOverflowSize; extendedCount = ConvertToBlocksizeCount(newCount); //We can't directly read this into the buffer provided //it may not be large enough to handle the blocksize shift //So we must read with the cryptobuffer and then blockcopy the result if (buffer.Length > extendedCount + start + cryptoOverflowSize) { //Read into buffer starting at the start requested + the overflow size bool result = await ReadAndDecrypt(buffer, start + cryptoOverflowSize, token, extendedCount) .ConfigureAwait(false); //if the read/decrypt failed then return 0 if (!result) { return(0); } FillCryptoOverflowWithExcess(buffer, start + cryptoOverflowSize, newCount, extendedCount); } else { //Read into buffer starting at the start requested + the overflow size bool result = await ReadAndDecrypt(ReadBuffer.Buffer, 0, token, extendedCount) .ConfigureAwait(false); //if the read/decrypt failed then return 0 if (!result) { return(0); } //Now we must BlockCopy the requested amount into the buffer BufferUtil.QuickUnsafeCopy(ReadBuffer.Buffer, 0, buffer, start + cryptoOverflowSize, newCount); FillCryptoOverflowWithExcess(ReadBuffer.Buffer, 0, newCount, extendedCount); } } } //never return the extended count, callers above shouldn't have to deal //with more than they asked for return(count); }