#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 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 { /// /// Represents a first-in, first-out collection of objects using a fixed buffer. /// /// /// The capacity of a is the number of elements the can hold. /// ByteCircularBuffer accepts null as a valid value for reference types and allows duplicate elements. /// The methods will remove the items that are returned from the ByteCircularBuffer. To view the contents of the ByteCircularBuffer without removing items, use the or methods. /// 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 /// /// Initializes a new instance of the class that is empty and has the specified initial capacity. /// /// The maximum capcity of the buffer. /// Thown if the is less than zero. 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 /// /// Gets or sets the total number of elements the internal data structure can hold. /// /// The total number of elements that the can contain. /// Thrown if the specified new capacity is smaller than the current contents of the buffer. 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; } } } /// /// Gets the index of the beginning of the buffer data. /// /// The index of the first element in the buffer. public int Head { get; protected set; } /// /// Gets a value indicating whether the buffer is empty. /// /// true if buffer is empty; otherwise, false. public virtual bool IsEmpty => this.Size == 0; /// /// Gets a value indicating whether the buffer is full. /// /// true if the buffer is full; otherwise, false. /// The property always returns false if the property is set to true. public virtual bool IsFull => this.Size == this.Capacity; /// /// Gets the number of elements contained in the . /// /// The number of elements contained in the . public int Size { get; protected set; } /// /// Gets the index of the end of the buffer data. /// /// The index of the last element in the buffer. public int Tail { get; protected set; } #endregion #region Public Members /// /// Removes all items from the . /// public void Clear() { this.Size = 0; this.Head = 0; this.Tail = 0; _buffer = new byte[this.Capacity]; } /// /// Determines whether the contains a specific value. /// /// The object to locate in the . /// true if is found in the ; otherwise, false. public bool Contains(byte item) { var bufferIndex = this.Head; var comparer = EqualityComparer.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; } /// /// Copies the entire to a compatible one-dimensional array, starting at the beginning of the target array. /// /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. public void CopyTo(byte[] array) { this.CopyTo(array, 0); } /// /// Copies the entire to a compatible one-dimensional array, starting at the specified index of the target array. /// /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. /// The zero-based index in at which copying begins. public void CopyTo(byte[] array, int arrayIndex) { this.CopyTo(this.Head, array, arrayIndex, Math.Min(this.Size, array.Length - arrayIndex)); } /// /// Copies a range of elements from the to a compatible one-dimensional array, starting at the specified index of the target array. /// /// The zero-based index in the source at which copying begins. /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. /// The zero-based index in at which copying begins. /// The number of elements to copy. 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; } } /// /// Removes and returns the specified number of objects from the beginning of the . /// /// The number of elements to remove and return from the . /// The objects that are removed from the beginning of the . 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; } /// /// Copies and removes the specified number elements from the to a compatible one-dimensional array, starting at the beginning of the target array. /// /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. /// The actual number of elements copied into . public int Get(byte[] array) { if (array.Length <= 0) throw new ArgumentOutOfRangeException("should greater than 0"); return this.Get(array, 0, array.Length); } /// /// Copies and removes the specified number elements from the to a compatible one-dimensional array, starting at the specified index of the target array. /// /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. /// The zero-based index in at which copying begins. /// The number of elements to copy. /// The actual number of elements copied into . 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; } /// /// Removes and returns the object at the beginning of the . /// /// The object that is removed from the beginning of the . /// Thrown if the buffer is empty. /// This method is similar to the method, but Peek does not modify the . 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; } /// /// Returns the object at the beginning of the without removing it. /// /// The object at the beginning of the . /// Thrown if the buffer is empty. public virtual byte Peek() { if (this.IsEmpty) { throw new InvalidOperationException("The buffer is empty."); } var item = _buffer[this.Head]; return item; } /// /// Returns the specified number of objects from the beginning of the . /// /// The number of elements to return from the . /// The objects that from the beginning of the . /// Thrown if the buffer is empty. 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; } /// /// Returns the object at the end of the without removing it. /// /// The object at the end of the . /// Thrown if the buffer is empty. 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; } /// /// Copies an entire compatible one-dimensional array to the . /// /// The one-dimensional that is the source of the elements copied to . The must have zero-based indexing. /// Thrown if buffer does not have sufficient capacity to put in new items. /// If plus the size of exceeds the capacity of the and the property is true, the oldest items in the are overwritten with . public int Put(byte[] array) { return this.Put(array, 0, array.Length); } /// /// Copies a range of elements from a compatible one-dimensional array to the . /// /// The one-dimensional that is the source of the elements copied to . The must have zero-based indexing. /// The zero-based index in at which copying begins. /// The number of elements to copy. /// Thrown if buffer does not have sufficient capacity to put in new items. /// If plus exceeds the capacity of the and the property is true, the oldest items in the are overwritten with . 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; } /// /// Adds a byte to the end of the . /// /// The byte to add to the . /// Thrown if buffer does not have sufficient capacity to put in new items. 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++; } } /// /// Increments the starting index of the data buffer in the . /// /// The number of elements to increment the data buffer start index by. 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; } /// /// Copies the elements to a new array. /// /// A new array containing elements copied from the . /// The 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 to its end. public byte[] ToArray() { var result = new byte[this.Size]; this.CopyTo(result); return result; } #endregion } }