Browse Source

remove loop buffer from aead encryptor

pull/2865/head
Student Main 5 years ago
parent
commit
bf56f40292
5 changed files with 104 additions and 631 deletions
  1. +56
    -30
      shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs
  2. +0
    -510
      shadowsocks-csharp/Encryption/CircularBuffer/ByteCircularBuffer.cs
  3. +0
    -2
      shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs
  4. +0
    -47
      test/CircularBufferTest.cs
  5. +48
    -42
      test/CryptographyTest.cs

+ 56
- 30
shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs View File

@@ -1,10 +1,8 @@
using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Text;
using Shadowsocks.Encryption.CircularBuffer;
using Shadowsocks.Controller;
using Shadowsocks.Encryption.Exception;
using Shadowsocks.Encryption.Stream;
@@ -19,7 +17,8 @@ namespace Shadowsocks.Encryption.AEAD
private static readonly byte[] InfoBytes = Encoding.ASCII.GetBytes(Info);
// every connection should create its own buffer
private ByteCircularBuffer buffer = new ByteCircularBuffer(MaxInputSize * 2);
private byte[] sharedBuffer = new byte[65536];
private int bufPtr = 0;
public const int ChunkLengthBytes = 2;
public const uint ChunkLengthMask = 0x3FFFu;
@@ -104,9 +103,10 @@ namespace Shadowsocks.Encryption.AEAD
public override void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength)
{
Debug.Assert(buffer != null, "_encCircularBuffer != null");
// push data
buf.CopyTo(sharedBuffer, bufPtr);
Span<byte> tmp = sharedBuffer.AsSpan(0, length + bufPtr);
buffer.Put(buf, 0, length);
outlength = 0;
logger.Debug("---Start Encryption");
if (!saltReady)
@@ -125,10 +125,13 @@ namespace Shadowsocks.Encryption.AEAD
tcpRequestSent = true;
// The first TCP request
byte[] encAddrBufBytes = new byte[AddressBufferLength + tagLen * 2 + ChunkLengthBytes];
byte[] addrBytes = buffer.Get(AddressBufferLength);
// read addr byte to encrypt
byte[] addrBytes = tmp.Slice(0, AddressBufferLength).ToArray();
tmp = tmp.Slice(AddressBufferLength);
int encAddrBufLength = ChunkEncrypt(addrBytes, encAddrBufBytes);
// ChunkEncrypt(addrBytes, AddressBufferLength, encAddrBufBytes, out encAddrBufLength);
Debug.Assert(encAddrBufLength == AddressBufferLength + tagLen * 2 + ChunkLengthBytes);
Array.Copy(encAddrBufBytes, 0, outbuf, outlength, encAddrBufLength);
outlength += encAddrBufLength;
logger.Debug($"_tcpRequestSent outlength {outlength}");
@@ -137,24 +140,34 @@ namespace Shadowsocks.Encryption.AEAD
// handle other chunks
while (true)
{
uint bufSize = (uint)buffer.Size;
// calculate next chunk size
int bufSize = tmp.Length;
if (bufSize <= 0) return;
var chunklength = (int)Math.Min(bufSize, ChunkLengthMask);
byte[] chunkBytes = buffer.Get(chunklength);
// read next chunk
byte[] chunkBytes = tmp.Slice(0, chunklength).ToArray();
tmp = tmp.Slice(chunklength);
byte[] encChunkBytes = new byte[chunklength + tagLen * 2 + ChunkLengthBytes];
int encChunkLength = ChunkEncrypt(chunkBytes, encChunkBytes);
// ChunkEncrypt(chunkBytes, chunklength, encChunkBytes, out encChunkLength);
Debug.Assert(encChunkLength == chunklength + tagLen * 2 + ChunkLengthBytes);
Buffer.BlockCopy(encChunkBytes, 0, outbuf, outlength, encChunkLength);
outlength += encChunkLength;
logger.Debug("chunks enc outlength " + outlength);
// check if we have enough space for outbuf
// if not, keep buf for next run, at this condition, buffer is not empty
if (outlength + TCPHandler.ChunkOverheadSize > TCPHandler.BufferSize)
{
logger.Debug("enc outbuf almost full, giving up");
// write rest data to head of shared buffer
tmp.CopyTo(sharedBuffer);
bufPtr = tmp.Length;
return;
}
bufSize = (uint)buffer.Size;
// check if buffer empty
bufSize = tmp.Length;
if (bufSize <= 0)
{
logger.Debug("No more data to encrypt, leaving");
@@ -166,24 +179,29 @@ namespace Shadowsocks.Encryption.AEAD
public override void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength)
{
Debug.Assert(buffer != null, "_decCircularBuffer != null");
int bufSize;
outlength = 0;
// drop all into buffer
buffer.Put(buf, 0, length);
buf.CopyTo(sharedBuffer, bufPtr);
Span<byte> tmp = buf.AsSpan(0, length + bufPtr);
int bufSize = tmp.Length;
logger.Debug("---Start Decryption");
if (!saltReady)
{
bufSize = buffer.Size;
// check if we get the leading salt
if (bufSize <= saltLen)
{
// need more
// need more, write back cache
tmp.CopyTo(sharedBuffer);
bufPtr = tmp.Length;
return;
}
saltReady = true;
byte[] salt = buffer.Get(saltLen);
// buffer.Get(saltLen);
byte[] salt = tmp.Slice(0, saltLen).ToArray();
tmp = tmp.Slice(saltLen);
InitCipher(salt, false, false);
logger.Debug("get salt len " + saltLen);
}
@@ -191,7 +209,7 @@ namespace Shadowsocks.Encryption.AEAD
// handle chunks
while (true)
{
bufSize = buffer.Size;
bufSize = tmp.Length;
// check if we have any data
if (bufSize <= 0)
{
@@ -203,12 +221,16 @@ namespace Shadowsocks.Encryption.AEAD
if (bufSize <= ChunkLengthBytes + tagLen)
{
// so we only have chunk length and its tag?
// wait more
tmp.CopyTo(sharedBuffer);
bufPtr = tmp.Length;
return;
}
#region Chunk Decryption
byte[] encLenBytes = buffer.Peek(ChunkLengthBytes + tagLen);
// byte[] encLenBytes = buffer.Peek(ChunkLengthBytes + tagLen);
byte[] encLenBytes = tmp.Slice(0, ChunkLengthBytes + tagLen).ToArray();
// try to dec chunk len
byte[] decChunkLenBytes = new byte[ChunkLengthBytes];
@@ -221,36 +243,40 @@ namespace Shadowsocks.Encryption.AEAD
throw new CryptoErrorException();
}
logger.Debug("Get the real chunk len:" + chunkLen);
bufSize = buffer.Size;
bufSize = tmp.Length;
if (bufSize < ChunkLengthBytes + tagLen /* we haven't remove them */+ chunkLen + tagLen)
{
logger.Debug("No more data to decrypt one chunk");
// write back length data
tmp.CopyTo(sharedBuffer);
bufPtr = tmp.Length;
return;
}
IncrementNonce();
// we have enough data to decrypt one chunk
// drop chunk len and its tag from buffer
buffer.Skip(ChunkLengthBytes + tagLen);
byte[] encChunkBytes = buffer.Get(chunkLen + tagLen);
// byte[] decChunkBytes = CipherDecrypt2(encChunkBytes);
// buffer.Skip(ChunkLengthBytes + tagLen);
tmp = tmp.Slice(ChunkLengthBytes + tagLen);
// byte[] encChunkBytes = buffer.Get(chunkLen + tagLen);
byte[] encChunkBytes = tmp.Slice(0, chunkLen + tagLen).ToArray();
tmp = tmp.Slice(chunkLen + tagLen);
int len = CipherDecrypt(outbuf.AsSpan().Slice(outlength), encChunkBytes);
int len = CipherDecrypt(outbuf.AsSpan(outlength), encChunkBytes);
IncrementNonce();
#endregion
// output to outbuf
// decChunkBytes.CopyTo(outbuf, outlength);
// Buffer.BlockCopy(decChunkBytes, 0, outbuf, outlength, (int)decChunkLen);
outlength += len;
logger.Debug("aead dec outlength " + outlength);
if (outlength + 100 > TCPHandler.BufferSize)
{
logger.Debug("dec outbuf almost full, giving up");
tmp.CopyTo(sharedBuffer);
bufPtr = tmp.Length;
return;
}
bufSize = buffer.Size;
bufSize = tmp.Length;
// check if we already done all of them
if (bufSize <= 0)
{


+ 0
- 510
shadowsocks-csharp/Encryption/CircularBuffer/ByteCircularBuffer.cs View File

@@ -1,510 +0,0 @@
#region Original License
//New BSD License(BSD)
//
//Copyright(c) 2014-2015 Cyotek Ltd
//Copyright(c) 2012, Alex Regueiro
//All rights reserved.
//
//Redistribution and use in source and binary forms, with or without
//modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of Cyotek nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//DISCLAIMED.IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
//DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections.Generic;
namespace Shadowsocks.Encryption.CircularBuffer
{
/// <summary>
/// Represents a first-in, first-out collection of objects using a fixed buffer.
/// </summary>
/// <remarks>
/// <para>The capacity of a <see cref="ByteCircularBuffer" /> is the number of elements the <see cref="ByteCircularBuffer"/> can hold. </para>
/// <para>ByteCircularBuffer accepts <c>null</c> as a valid value for reference types and allows duplicate elements.</para>
/// <para>The <see cref="Get()"/> methods will remove the items that are returned from the ByteCircularBuffer. To view the contents of the ByteCircularBuffer without removing items, use the <see cref="Peek()"/> or <see cref="PeekLast"/> methods.</para>
/// </remarks>
public class ByteCircularBuffer
{
// based on http://circularbuffer.codeplex.com/
// http://en.wikipedia.org/wiki/Circular_buffer
// modified from https://github.com/cyotek/Cyotek.Collections.Generic.CircularBuffer
// some code taken from https://github.com/xorxornop/RingBuffer
// and https://github.com/xorxornop/PerfCopy
#region Instance Fields
private byte[] _buffer;
private int _capacity;
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ByteCircularBuffer"/> class that is empty and has the specified initial capacity.
/// </summary>
/// <param name="capacity">The maximum capcity of the buffer.</param>
/// <exception cref="System.ArgumentException">Thown if the <paramref name="capacity"/> is less than zero.</exception>
public ByteCircularBuffer(int capacity)
{
if (capacity < 0)
{
throw new ArgumentException("The buffer capacity must be greater than or equal to zero.",
nameof(capacity));
}
_buffer = new byte[capacity];
this.Capacity = capacity;
this.Size = 0;
this.Head = 0;
this.Tail = 0;
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the total number of elements the internal data structure can hold.
/// </summary>
/// <value>The total number of elements that the <see cref="ByteCircularBuffer"/> can contain.</value>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown if the specified new capacity is smaller than the current contents of the buffer.</exception>
public int Capacity
{
get { return _capacity; }
set
{
if (value != _capacity)
{
if (value < this.Size)
{
throw new ArgumentOutOfRangeException(nameof(value), value,
"The new capacity must be greater than or equal to the buffer size.");
}
var newBuffer = new byte[value];
if (this.Size > 0)
{
this.CopyTo(newBuffer);
}
_buffer = newBuffer;
_capacity = value;
}
}
}
/// <summary>
/// Gets the index of the beginning of the buffer data.
/// </summary>
/// <value>The index of the first element in the buffer.</value>
public int Head { get; protected set; }
/// <summary>
/// Gets a value indicating whether the buffer is empty.
/// </summary>
/// <value><c>true</c> if buffer is empty; otherwise, <c>false</c>.</value>
public virtual bool IsEmpty => this.Size == 0;
/// <summary>
/// Gets a value indicating whether the buffer is full.
/// </summary>
/// <value><c>true</c> if the buffer is full; otherwise, <c>false</c>.</value>
/// <remarks>The <see cref="IsFull"/> property always returns <c>false</c> if the <see cref="AllowOverwrite"/> property is set to <c>true</c>.</remarks>
public virtual bool IsFull => this.Size == this.Capacity;
/// <summary>
/// Gets the number of elements contained in the <see cref="ByteCircularBuffer"/>.
/// </summary>
/// <value>The number of elements contained in the <see cref="ByteCircularBuffer"/>.</value>
public int Size { get; protected set; }
/// <summary>
/// Gets the index of the end of the buffer data.
/// </summary>
/// <value>The index of the last element in the buffer.</value>
public int Tail { get; protected set; }
#endregion
#region Public Members
/// <summary>
/// Removes all items from the <see cref="ByteCircularBuffer" />.
/// </summary>
public void Clear()
{
this.Size = 0;
this.Head = 0;
this.Tail = 0;
_buffer = new byte[this.Capacity];
}
/// <summary>
/// Determines whether the <see cref="ByteCircularBuffer" /> contains a specific value.
/// </summary>
/// <param name="item">The object to locate in the <see cref="ByteCircularBuffer" />.</param>
/// <returns><c>true</c> if <paramref name="item" /> is found in the <see cref="ByteCircularBuffer" />; otherwise, <c>false</c>.</returns>
public bool Contains(byte item)
{
var bufferIndex = this.Head;
var comparer = EqualityComparer<byte>.Default;
var result = false;
for (int i = 0; i < this.Size; i++, bufferIndex++)
{
if (bufferIndex == this.Capacity)
{
bufferIndex = 0;
}
if (comparer.Equals(_buffer[bufferIndex], item))
{
result = true;
break;
}
}
return result;
}
/// <summary>
/// Copies the entire <see cref="ByteCircularBuffer"/> to a compatible one-dimensional array, starting at the beginning of the target array.
/// </summary>
/// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
public void CopyTo(byte[] array)
{
this.CopyTo(array, 0);
}
/// <summary>
/// Copies the entire <see cref="ByteCircularBuffer"/> to a compatible one-dimensional array, starting at the specified index of the target array.
/// </summary>
/// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
/// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
public void CopyTo(byte[] array, int arrayIndex)
{
this.CopyTo(this.Head, array, arrayIndex, Math.Min(this.Size, array.Length - arrayIndex));
}
/// <summary>
/// Copies a range of elements from the <see cref="ByteCircularBuffer"/> to a compatible one-dimensional array, starting at the specified index of the target array.
/// </summary>
/// <param name="index">The zero-based index in the source <see cref="ByteCircularBuffer"/> at which copying begins.</param>
/// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
/// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
/// <param name="count">The number of elements to copy.</param>
public virtual void CopyTo(int index, byte[] array, int arrayIndex, int count)
{
if (count > this.Size)
{
throw new ArgumentOutOfRangeException(nameof(count), count,
"The read count cannot be greater than the buffer size.");
}
var startAnchor = index;
var dstIndex = arrayIndex;
while (count > 0)
{
int chunk = Math.Min(Capacity - startAnchor, count);
Buffer.BlockCopy(_buffer, startAnchor, array, dstIndex, chunk);
startAnchor = (startAnchor + chunk == Capacity) ? 0 : startAnchor + chunk;
dstIndex += chunk;
count -= chunk;
}
}
/// <summary>
/// Removes and returns the specified number of objects from the beginning of the <see cref="ByteCircularBuffer"/>.
/// </summary>
/// <param name="count">The number of elements to remove and return from the <see cref="ByteCircularBuffer"/>.</param>
/// <returns>The objects that are removed from the beginning of the <see cref="ByteCircularBuffer"/>.</returns>
public byte[] Get(int count)
{
if (count <= 0) throw new ArgumentOutOfRangeException("should greater than 0");
var result = new byte[count];
this.Get(result);
return result;
}
/// <summary>
/// Copies and removes the specified number elements from the <see cref="ByteCircularBuffer"/> to a compatible one-dimensional array, starting at the beginning of the target array.
/// </summary>
/// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
/// <returns>The actual number of elements copied into <paramref name="array"/>.</returns>
public int Get(byte[] array)
{
if (array.Length <= 0) throw new ArgumentOutOfRangeException("should greater than 0");
return this.Get(array, 0, array.Length);
}
/// <summary>
/// Copies and removes the specified number elements from the <see cref="ByteCircularBuffer"/> to a compatible one-dimensional array, starting at the specified index of the target array.
/// </summary>
/// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
/// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
/// <param name="count">The number of elements to copy.</param>
/// <returns>The actual number of elements copied into <paramref name="array"/>.</returns>
public virtual int Get(byte[] array, int arrayIndex, int count)
{
if (arrayIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Negative offset specified. Offsets must be positive.");
}
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count), "Negative count specified. Count must be positive.");
}
if (count > this.Size)
{
throw new ArgumentException("Ringbuffer contents insufficient for take/read operation.", nameof(count));
}
if (array.Length < arrayIndex + count)
{
throw new ArgumentException("Destination array too small for requested output.");
}
var bytesCopied = 0;
var dstIndex = arrayIndex;
while (count > 0)
{
int chunk = Math.Min(Capacity - this.Head, count);
Buffer.BlockCopy(_buffer, this.Head, array, dstIndex, chunk);
this.Head = (this.Head + chunk == Capacity) ? 0 : this.Head + chunk;
this.Size -= chunk;
dstIndex += chunk;
bytesCopied += chunk;
count -= chunk;
}
return bytesCopied;
}
/// <summary>
/// Removes and returns the object at the beginning of the <see cref="ByteCircularBuffer"/>.
/// </summary>
/// <returns>The object that is removed from the beginning of the <see cref="ByteCircularBuffer"/>.</returns>
/// <exception cref="System.InvalidOperationException">Thrown if the buffer is empty.</exception>
/// <remarks>This method is similar to the <see cref="Peek()"/> method, but <c>Peek</c> does not modify the <see cref="ByteCircularBuffer"/>.</remarks>
public virtual byte Get()
{
if (this.IsEmpty)
{
throw new InvalidOperationException("The buffer is empty.");
}
var item = _buffer[this.Head];
if (++this.Head == this.Capacity)
{
this.Head = 0;
}
this.Size--;
return item;
}
/// <summary>
/// Returns the object at the beginning of the <see cref="ByteCircularBuffer"/> without removing it.
/// </summary>
/// <returns>The object at the beginning of the <see cref="ByteCircularBuffer"/>.</returns>
/// <exception cref="System.InvalidOperationException">Thrown if the buffer is empty.</exception>
public virtual byte Peek()
{
if (this.IsEmpty)
{
throw new InvalidOperationException("The buffer is empty.");
}
var item = _buffer[this.Head];
return item;
}
/// <summary>
/// Returns the specified number of objects from the beginning of the <see cref="ByteCircularBuffer"/>.
/// </summary>
/// <param name="count">The number of elements to return from the <see cref="ByteCircularBuffer"/>.</param>
/// <returns>The objects that from the beginning of the <see cref="ByteCircularBuffer"/>.</returns>
/// <exception cref="System.InvalidOperationException">Thrown if the buffer is empty.</exception>
public virtual byte[] Peek(int count)
{
if (this.IsEmpty)
{
throw new InvalidOperationException("The buffer is empty.");
}
var items = new byte[count];
this.CopyTo(items);
return items;
}
/// <summary>
/// Returns the object at the end of the <see cref="ByteCircularBuffer"/> without removing it.
/// </summary>
/// <returns>The object at the end of the <see cref="ByteCircularBuffer"/>.</returns>
/// <exception cref="System.InvalidOperationException">Thrown if the buffer is empty.</exception>
public virtual byte PeekLast()
{
int bufferIndex;
if (this.IsEmpty)
{
throw new InvalidOperationException("The buffer is empty.");
}
if (this.Tail == 0)
{
bufferIndex = this.Size - 1;
}
else
{
bufferIndex = this.Tail - 1;
}
var item = _buffer[bufferIndex];
return item;
}
/// <summary>
/// Copies an entire compatible one-dimensional array to the <see cref="ByteCircularBuffer"/>.
/// </summary>
/// <param name="array">The one-dimensional <see cref="Array"/> that is the source of the elements copied to <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
/// <exception cref="System.InvalidOperationException">Thrown if buffer does not have sufficient capacity to put in new items.</exception>
/// <remarks>If <see cref="Size"/> plus the size of <paramref name="array"/> exceeds the capacity of the <see cref="ByteCircularBuffer"/> and the <see cref="AllowOverwrite"/> property is <c>true</c>, the oldest items in the <see cref="ByteCircularBuffer"/> are overwritten with <paramref name="array"/>.</remarks>
public int Put(byte[] array)
{
return this.Put(array, 0, array.Length);
}
/// <summary>
/// Copies a range of elements from a compatible one-dimensional array to the <see cref="ByteCircularBuffer"/>.
/// </summary>
/// <param name="array">The one-dimensional <see cref="Array"/> that is the source of the elements copied to <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
/// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
/// <param name="count">The number of elements to copy.</param>
/// <exception cref="System.InvalidOperationException">Thrown if buffer does not have sufficient capacity to put in new items.</exception>
/// <remarks>If <see cref="Size"/> plus <paramref name="count"/> exceeds the capacity of the <see cref="ByteCircularBuffer"/> and the <see cref="AllowOverwrite"/> property is <c>true</c>, the oldest items in the <see cref="ByteCircularBuffer"/> are overwritten with <paramref name="array"/>.</remarks>
public virtual int Put(byte[] array, int arrayIndex, int count)
{
if (count <= 0) throw new ArgumentOutOfRangeException(nameof(count), "Count must be positive.");
if (this.Size + count > this.Capacity)
{
throw new InvalidOperationException("The buffer does not have sufficient capacity to put new items.");
}
if (array.Length < arrayIndex + count)
{
throw new ArgumentException("Source array too small for requested input.");
}
var srcIndex = arrayIndex;
var bytesToProcess = count;
while (bytesToProcess > 0)
{
int chunk = Math.Min(Capacity - Tail, bytesToProcess);
Buffer.BlockCopy(array, srcIndex, _buffer, Tail, chunk);
Tail = (Tail + chunk == Capacity) ? 0 : Tail + chunk;
this.Size += chunk;
srcIndex += chunk;
bytesToProcess -= chunk;
}
return count;
}
/// <summary>
/// Adds a byte to the end of the <see cref="ByteCircularBuffer"/>.
/// </summary>
/// <param name="item">The byte to add to the <see cref="ByteCircularBuffer"/>. </param>
/// <exception cref="System.InvalidOperationException">Thrown if buffer does not have sufficient capacity to put in new items.</exception>
public virtual void Put(byte item)
{
if (IsFull)
{
throw new InvalidOperationException("The buffer does not have sufficient capacity to put new items.");
}
_buffer[this.Tail] = item;
this.Tail++;
if (this.Size == this.Capacity)
{
this.Head++;
if (this.Head >= this.Capacity)
{
this.Head -= this.Capacity;
}
}
if (this.Tail == this.Capacity)
{
this.Tail = 0;
}
if (this.Size != this.Capacity)
{
this.Size++;
}
}
/// <summary>
/// Increments the starting index of the data buffer in the <see cref="ByteCircularBuffer"/>.
/// </summary>
/// <param name="count">The number of elements to increment the data buffer start index by.</param>
public void Skip(int count)
{
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count), "Negative count specified. Count must be positive.");
}
if (count > this.Size)
{
throw new ArgumentException("Ringbuffer contents insufficient for operation.", nameof(count));
}
// Modular division gives new offset position
this.Head = (this.Head + count) % Capacity;
this.Size -= count;
}
/// <summary>
/// Copies the <see cref="ByteCircularBuffer"/> elements to a new array.
/// </summary>
/// <returns>A new array containing elements copied from the <see cref="ByteCircularBuffer"/>.</returns>
/// <remarks>The <see cref="ByteCircularBuffer"/> is not modified. The order of the elements in the new array is the same as the order of the elements from the beginning of the <see cref="ByteCircularBuffer"/> to its end.</remarks>
public byte[] ToArray()
{
var result = new byte[this.Size];
this.CopyTo(result);
return result;
}
#endregion
}
}

+ 0
- 2
shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs View File

@@ -1,6 +1,4 @@
using NLog;
using Shadowsocks.Controller;
using Shadowsocks.Encryption.CircularBuffer;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;


+ 0
- 47
test/CircularBufferTest.cs View File

@@ -1,47 +0,0 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shadowsocks.Encryption.CircularBuffer;
using System;
using System.Collections.Generic;
using System.Text;

namespace Shadowsocks.Test
{
[TestClass]
public class CircularBufferTest
{
void ArrayEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual)
{
var l = expected.GetEnumerator();
var r = actual.GetEnumerator();
int p = 0;
while (l.MoveNext() && r.MoveNext())
{
Assert.AreEqual(l.Current, r.Current, $"not equal at {p}");
p++;
}
if (l.MoveNext()) Assert.Fail("expected longer than actual");
else if (r.MoveNext()) Assert.Fail("expected shorter than actual");
}
[TestMethod]
public void GetPut()
{
var c = new ByteCircularBuffer(8);
c.Put(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
Assert.AreEqual(c.Head, 0);
Assert.AreEqual(c.Tail, 0);
c.Get(4);
Assert.AreEqual(c.Head, 4);
Assert.AreEqual(c.Tail, 0);
Assert.AreEqual(c.Get(), 5);
c.Put(new byte[] { 1, 2, 3, 4, 5 });
Assert.AreEqual(c.Head, 5);
Assert.AreEqual(c.Tail, 5);
var content = c.Get(8);
ArrayEqual(content, new byte[] { 6, 7, 8, 1, 2, 3, 4, 5 });
c.Put(10);
var b = new byte[1];
c.Get(b);
Assert.AreEqual(b[0], 10);
}
}
}

+ 48
- 42
test/CryptographyTest.cs View File

@@ -13,34 +13,6 @@ namespace Shadowsocks.Test
{
Random random = new Random();
private void ArrayEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual, string msg = "")
{
var e1 = expected.GetEnumerator();
var e2 = actual.GetEnumerator();
int ctr = 0;
while (true)
{
var e1next = e1.MoveNext();
var e2next = e2.MoveNext();
if (e1next && e2next)
{
Assert.AreEqual(e1.Current, e2.Current, "at " + ctr);
}
else if (!e1next && !e2next)
{
return;
}
else if (!e1next)
{
Assert.Fail($"actual longer than expected ({ctr}) {msg}");
}
else
{
Assert.Fail($"actual shorter than expected ({ctr}) {msg}");
}
}
}
[TestMethod]
public void TestMD5()
@@ -56,6 +28,7 @@ namespace Shadowsocks.Test
}
}
#region Encryptor test tools
private void SingleEncryptionTestCase(IEncryptor encryptor, IEncryptor decryptor, int length)
{
RNG.Reload();
@@ -70,14 +43,6 @@ namespace Shadowsocks.Test
ArrayEqual<byte>(plain.AsSpan(0, length).ToArray(), plain2.AsSpan(0, length).ToArray());
}
private void RunEncryptionRound(IEncryptor encryptor, IEncryptor decryptor)
{
SingleEncryptionTestCase(encryptor, decryptor, 16384);
SingleEncryptionTestCase(encryptor, decryptor, 7);
SingleEncryptionTestCase(encryptor, decryptor, 1000);
SingleEncryptionTestCase(encryptor, decryptor, 12333);
}
const string password = "barfoo!";
private void RunSingleEncryptionThread(Type enc, Type dec, string method)
@@ -104,6 +69,35 @@ namespace Shadowsocks.Test
}
}
private void ArrayEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual, string msg = "")
{
var e1 = expected.GetEnumerator();
var e2 = actual.GetEnumerator();
int ctr = 0;
while (true)
{
var e1next = e1.MoveNext();
var e2next = e2.MoveNext();
if (e1next && e2next)
{
Assert.AreEqual(e1.Current, e2.Current, "at " + ctr);
}
else if (!e1next && !e2next)
{
return;
}
else if (!e1next)
{
Assert.Fail($"actual longer than expected ({ctr}) {msg}");
}
else
{
Assert.Fail($"actual shorter than expected ({ctr}) {msg}");
}
}
}
private static bool encryptionFailed = false;
private void TestEncryptionMethod(Type enc, string method)
@@ -128,9 +122,19 @@ namespace Shadowsocks.Test
}
Assert.IsFalse(encryptionFailed);
}
#endregion
// encryption test cases
private void RunEncryptionRound(IEncryptor encryptor, IEncryptor decryptor)
{
SingleEncryptionTestCase(encryptor, decryptor, 16384);
SingleEncryptionTestCase(encryptor, decryptor, 7); // for not aligned data
SingleEncryptionTestCase(encryptor, decryptor, 1000);
SingleEncryptionTestCase(encryptor, decryptor, 12333);
}
[TestMethod]
public void TestAesGcmNativeAEADEncryption()
public void TestAEADAesGcmNativeEncryption()
{
TestEncryptionMethod(typeof(AEADAesGcmNativeEncryptor), "aes-128-gcm");
TestEncryptionMethod(typeof(AEADAesGcmNativeEncryptor), "aes-192-gcm");
@@ -138,23 +142,25 @@ namespace Shadowsocks.Test
}
[TestMethod]
public void TestNaClAEADEncryption()
public void TestAEADNaClEncryption()
{
TestEncryptionMethod(typeof(AEADNaClEncryptor), "chacha20-ietf-poly1305");
TestEncryptionMethod(typeof(AEADNaClEncryptor), "xchacha20-ietf-poly1305");
}
[TestMethod]
public void TestNativeEncryption()
public void TestStreamNativeEncryption()
{
TestEncryptionMethod(typeof(StreamPlainNativeEncryptor), "plain");
// TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4");
TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4");
TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4-md5");
}
[TestMethod]
public void TestStreamAesBouncyCastleEncryption()
public void TestStreamAesCfbBouncyCastleEncryption()
{
TestEncryptionMethod(typeof(StreamAesBouncyCastleEncryptor), "aes-128-cfb");
TestEncryptionMethod(typeof(StreamAesBouncyCastleEncryptor), "aes-192-cfb");
TestEncryptionMethod(typeof(StreamAesBouncyCastleEncryptor), "aes-256-cfb");
}
[TestMethod]


Loading…
Cancel
Save