From dd169fddd60ef2bc61da309b2e5386746aafc8aa Mon Sep 17 00:00:00 2001 From: Jason Curl Date: Sun, 5 Jul 2020 15:33:32 +0200 Subject: [PATCH] CircularBuffer: Check length before decoding with System.Text Trying to decode a zero length source buffer on Mono may result in an ArgumentException (observed with ISO-8859-15, but not with UTF8/ASCII). The exception raised also has no parameter set, thus a check needs to be done to ensure that the ParamName is not null, else this situation will result in a double fault with a NullReferenceException also. Issue: DOTNET-194 --- CHANGES.md | 7 + README.md | 33 +- code/Datastructures/CircularBuffer.cs | 829 +++++++++++------- code/Properties/AssemblyInfo.cs | 4 +- code/SerialPortStream-net40.csproj | 2 +- code/SerialPortStream-net45.csproj | 2 +- code/SerialPortStream-netstandard15.csproj | 2 +- code/SerialPortStream.nuspec | 2 +- test/DatastructuresTest/CircularBufferTest.cs | 234 ++++- 9 files changed, 743 insertions(+), 372 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index da3f2c0c..201ce2da 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # List of Changes with Releases +## Version 2.2.2 (libnserial 1.1.4) + +Bugfixes + +* DOTNET-194: Prevent exceptions when converting bytes to chars when using + ISO-8859-15. + ## Version 2.2.1 (libnserial 1.1.4) Bugfixes diff --git a/README.md b/README.md index 68d2451c..49901b81 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,32 @@ example. These notes are for version 2.x, which is a new design based on version 1.x that enhances portability and fixes bugs. See the end of these notes for differences. +* 1.0 Why another Serial Port implementation +* 2.0 Goals + * 2.1 Issues with MS Serial Port +* 3.0 System Requirements + * 3.1 Tested + * 3.2 Compatibility + * 3.2.1 Mono Framework (Linux Only) + * 3.2.2 Microsoft Compact Framework (not supported) + * 3.2.3 Untested, but should work +* 4.0 Installation + * 4.1 Windows + * 4.2 Linux +* 5.0 Extra Features + * 5.1 Reading and Writing - Buffering +* 6.0 Known Issues + * 6.1 Windows + * 6.1.1 Driver Specific Issues on Windows + * 6.1.1.1 Flow Control + * 6.2 Linux + * 6.2.1 Mono on non-Windows Platforms + * 6.2.2 Driver Specific Issues on Linux + * 6.2.2.1 Parity Errors + * 6.2.2.2 Garbage Data on Open + * 6.2.2.3 Monitoring Pins and Timing Resolution + * 6.2.2.4 Close Times with Flow Control + ## 1.0 Why another Serial Port implementation Microsoft and Mono already provides a reasonable implementation for accessing @@ -87,7 +113,10 @@ See later in these notes for known issues and changes. ### 3.2 Compatibility -#### 3.2.1 Mono Framework +#### 3.2.1 Mono Framework (Linux Only) + +Only Mono on Linux is supported. Embedded platforms are not supported. You are +welcome to make a patch with appropriate test cases. You should use the latest version of Mono. Version 3.2.8 has significant bugs and will not work (Ubuntu 14.04 ships with this). Use the latest version of Mono @@ -97,7 +126,7 @@ For instructions on how to install the latest Mono for your system, refer to [Install Mono On Linux](http://www.mono-project.com/docs/getting-started/install/linux/). -#### 3.2.2 Microsoft Compact Framework +#### 3.2.2 Microsoft Compact Framework (not supported) SerialPortStream is not designed for the Compact Framework. diff --git a/code/Datastructures/CircularBuffer.cs b/code/Datastructures/CircularBuffer.cs index 8a1708ea..cc4f26da 100644 --- a/code/Datastructures/CircularBuffer.cs +++ b/code/Datastructures/CircularBuffer.cs @@ -5,28 +5,26 @@ namespace RJCP.Datastructures { using System; - using System.Text; using System.Diagnostics; + using System.Text; /// - /// A simple datastructure to manage an array as a circular buffer. + /// A simple data structure to manage an array as a circular buffer. /// /// - /// This class provides simple methods for abstracting a circular buffer. A circular buffer - /// allows for faster access of data by avoiding potential copy operations for data that - /// is at the beginning. - /// Stream data structures can benefit from this data structure by allocating a - /// single block on the heap of an arbitrary size. If the stream is long-lived the benefits - /// are larger. In the .NET framework (4.0 and earlier), all allocations of data structures - /// that are 80kb and larger are automatically allocated on the heap. The heap is not - /// garbage collected like smaller objects. Instead, new elements are added to the heap - /// in an incremental fashion. It is theoretically possible to exhaust all memory in an - /// application by allocating and deallocating regularly on a heap if such a new heap element - /// requires space and there is not a single block large enough. By using the - /// with the type T as byte, you can preallocate a buffer for a stream - /// of any reasonable size (as a simple example 5MB). That block is allocated once and - /// remains for the lifetime of the stream. No time will be allocated for compacting or - /// garbage collection. + /// This class provides simple methods for abstracting a circular buffer. A circular buffer allows for faster access + /// of data by avoiding potential copy operations for data that is at the beginning. + /// + /// Stream data structures can benefit from this data structure by allocating a single block on the heap of an + /// arbitrary size. If the stream is long-lived the benefits are larger. In the .NET framework (4.0 and earlier), + /// all allocations of data structures that are 80kb and larger are automatically allocated on the heap. The heap is + /// not garbage collected like smaller objects. Instead, new elements are added to the heap in an incremental + /// fashion. It is theoretically possible to exhaust all memory in an application by allocating and deallocating + /// regularly on a heap if such a new heap element requires space and there is not a single block large enough. By + /// using the with the type T as byte, you can preallocate a buffer + /// for a stream of any reasonable size (as a simple example 5MB). That block is allocated once and remains for the + /// lifetime of the stream. No time will be allocated for compacting or garbage collection. + /// /// /// Type to use for the array. [DebuggerDisplay("Start = {Start}; Length = {Length}; Free = {Free}")] @@ -54,8 +52,10 @@ internal class CircularBuffer /// Allocate an Array of type T[] of particular capacity. /// /// Size of array to allocate. + /// must be positive. public CircularBuffer(int capacity) { + if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity), "capacity must be positive"); m_Array = new T[capacity]; m_Start = 0; m_Count = 0; @@ -64,15 +64,20 @@ public CircularBuffer(int capacity) /// /// Circular buffer based on an already allocated array. /// + /// Array (zero indexed) to allocate. + /// + /// may not be . + /// + /// must have at least one element; /// - /// The array is used as the storage for the circular buffer. No copy of the array - /// is made. The initial index in the circular buffer is index 0 in the array. The - /// array is assumed to be completely used (i.e. it is initialised with zero bytes - /// Free). + /// The array is used as the storage for the circular buffer. No copy of the array is made. The initial index in + /// the circular buffer is index 0 in the array. The array is assumed to be completely used (i.e. it is + /// initialized with zero bytes Free). /// - /// Array (zero indexed) to allocate. public CircularBuffer(T[] array) { + if (array == null) throw new ArgumentNullException(nameof(array)); + if (array.Length == 0) throw new ArgumentException("Array must have at least one element", nameof(array)); m_Array = array; m_Start = 0; m_Count = array.Length; @@ -81,20 +86,26 @@ public CircularBuffer(T[] array) /// /// Circular buffer based on an already allocated array. /// - /// - /// The array is used as the storage for the circular buffer. No copy of the array - /// is made, only a reference. The initial index in the array is 0. The value - /// count sets the initial length of the array. So an initial count - /// of zero would imply an empty circular buffer. - /// /// Array (zero indexed) to allocate. /// Length of data in array, beginning from offset 0. + /// + /// Initial must be within range of + /// + /// must have at least one element; + /// + /// may not be . + /// + /// + /// The array is used as the storage for the circular buffer. No copy of the array is made, only a reference. + /// The initial index in the array is 0. The value sets the initial length of the + /// array. So an initial of zero would imply an empty circular buffer. + /// public CircularBuffer(T[] array, int count) { - if (count < 0) throw new ArgumentOutOfRangeException("count", "must be positive"); - if (count > array.Length) - throw new ArgumentOutOfRangeException("Count (" + count + - ") exceeds the array boundaries (" + array.Length + ")"); + if (array == null) throw new ArgumentNullException(nameof(array)); + if (array.Length == 0) throw new ArgumentException("Array must have at least one element", nameof(array)); + if (count < 0 || count > array.Length) + throw new ArgumentOutOfRangeException(nameof(count), "Count must be within range of the array"); m_Array = array; m_Start = 0; @@ -104,28 +115,35 @@ public CircularBuffer(T[] array, int count) /// /// Circular buffer based on an already allocated array. /// - /// - /// The array is used as the storage for the circular buffer. No copy of the array - /// is made, only a reference. The offset is defined to be the first entry in the - /// circular buffer. This may be any value from zero to the last index - /// (Array.Length - 1). The value count is the amount of data in the - /// array, and it may cause wrapping (so that by setting offset near the end, a value - /// of count may be set so that data can be considered at the end and beginning of - /// the array given). - /// /// Array (zero indexed) to allocate. /// Offset of first byte in the array. - /// Length of data in array, wrapping to the start of the array. + /// + /// Length of data in , wrapping to the start of the . + /// + /// must have at least one element; + /// + /// may not be . + /// + /// + /// must be within range of ; + /// - or - + /// exceeds the boundaries. + /// + /// + /// The array is used as the storage for the circular buffer. No copy of the array is made, only a reference. + /// The is defined to be the first entry in the circular buffer. This may be any value + /// from zero to the last index ( Array.Length - 1). The value is the amount of + /// data in the array, and it may cause wrapping (so that by setting offset near the end, a value of count may + /// be set so that data can be considered at the end and beginning of the array given). + /// public CircularBuffer(T[] array, int offset, int count) { - if (count < 0) throw new ArgumentOutOfRangeException("count", "must be positive"); - if (offset < 0) throw new ArgumentOutOfRangeException("offset", "must be positive"); - if (count > array.Length) - throw new ArgumentOutOfRangeException("Count (" + count + - ") exceeds the array boundaries (" + array.Length + ")"); - if (offset >= array.Length) - throw new ArgumentOutOfRangeException("Offset (" + offset + - ") exceeds the array boundaries (" + (array.Length - 1) + ")"); + if (array == null) throw new ArgumentNullException(nameof(array)); + if (array.Length == 0) throw new ArgumentException("Array must have at least one element", nameof(array)); + if (count < 0 || count > array.Length) + throw new ArgumentOutOfRangeException(nameof(count), "must be within range of the array"); + if (offset < 0 || offset >= array.Length) + throw new ArgumentOutOfRangeException(nameof(offset), "exceeds array boundaries"); m_Array = array; m_Start = offset; @@ -141,8 +159,7 @@ public CircularBuffer(T[] array, int offset, int count) /// Get end index into array where data ends. /// /// - /// This property is useful to know from what element in the underlying array - /// that data can be written to. + /// This property is useful to know from what element in the underlying array that data can be written to. /// public int End { get { return (m_Start + m_Count) % m_Array.Length; } } @@ -150,8 +167,8 @@ public CircularBuffer(T[] array, int offset, int count) /// Get total length of data in array. /// /// - /// Returns the amount of allocated data in the circular buffer. The - /// following rule applies: + = . + /// Returns the amount of allocated data in the circular buffer. The following rule applies: + /// + = . /// public int Length { get { return m_Count; } } @@ -159,8 +176,8 @@ public CircularBuffer(T[] array, int offset, int count) /// Get total free data in array. /// /// - /// Returns the total amount of free elements in the circular buffer. The - /// following rule applies: + = . + /// Returns the total amount of free elements in the circular buffer. The following rule applies: + /// + = . /// public int Free { get { return m_Array.Length - m_Count; } } @@ -168,39 +185,39 @@ public CircularBuffer(T[] array, int offset, int count) /// Get the total capacity of the array. /// /// - /// Get the total number of elements allocated for the underlying array of the - /// circular buffer. The following rule applies: - /// + = . + /// Get the total number of elements allocated for the underlying array of the circular buffer. The following + /// rule applies: + = . /// public int Capacity { get { return m_Array.Length; } } /// /// Convert an index from the start of the data to read to an array index. /// - /// Index in circular buffer, where an index of 0 is equivalent - /// to the property. + /// + /// Index in circular buffer, where an index of 0 is equivalent to the property. + /// /// Index in array that can be used in array based operations. public int ToArrayIndex(int index) { return (m_Start + index) % m_Array.Length; } /// - /// Get length of continuous available space from the current position to the end of the array - /// or until the buffer is full. + /// Get length of continuous available space from the current position to the end of the array or until the + /// buffer is full. /// /// - /// This function is useful if you need to pass the array to another function that will - /// then fill the contents of the buffer. You would pass as the offset for - /// where writing the data should start, and WriteLength as the length of buffer - /// space available until the end of the array buffer. After the read operation that - /// writes in to your buffer, the array is completely full, or until the end of the array. - /// Such a property is necessary in case that the free space wraps around the - /// buffer. Where below X is your stream you wish to read from, b is the - /// circular buffer instantiated as the type CircularBuffer{T}. + /// This function is useful if you need to pass the array to another function that will then fill the contents + /// of the buffer. You would pass as the offset for where writing the data should start, and + /// WriteLength as the length of buffer space available until the end of the array buffer. After the read + /// operation that writes in to your buffer, the array is completely full, or until the end of the array. + /// + /// Such a property is necessary in case that the free space wraps around the buffer. Where below X is + /// your stream you wish to read from, b is the circular buffer instantiated as the type + /// CircularBuffer{T}. /// - /// c = X.Read(b.Array, b.End, b.WriteLength); - /// b.Produce(c); + ///c = X.Read(b.Array, b.End, b.WriteLength); + ///b.Produce(c); /// - /// If the property WriteLength is not zero, then there is space in the buffer - /// to read data. + /// If the property WriteLength is not zero, then there is space in the buffer to read data. + /// /// public int WriteLength { @@ -212,14 +229,14 @@ public int WriteLength } /// - /// Get the length of the continuous amount of data that can be read in a single copy - /// operation from the start of the buffer data. + /// Get the length of the continuous amount of data that can be read in a single copy operation from the start + /// of the buffer data. /// /// - /// This function is useful if you need to pass the array to another function that will - /// use the contents of the array. You would pass as the offset for reading - /// data and as the count. Then based on the amount of data operated - /// on, you would free space with (ReadLength). + /// This function is useful if you need to pass the array to another function that will use the contents of the + /// array. You would pass as the offset for reading data and as the + /// count. Then based on the amount of data operated on, you would free space with + /// (ReadLength). /// public int ReadLength { @@ -231,35 +248,33 @@ public int ReadLength } /// - /// Given an offset, calculate the length of data that can be read until the end of the - /// block. + /// Given an offset, calculate the length of data that can be read until the end of the block. /// + /// The offset into the circular buffer to test for the read length. + /// Length of the block that can be read from . + /// The may not be negative. /// - /// Similar to the property ReadLength, this function takes an argument offset - /// which is used to determine the length of data that can be read from that offset, until - /// either the end of the block, or the end of the buffer. - /// This function is useful if you want to read a block of data, not starting from - /// the offset 0 (and you don't want to consume the data before hand to reach an offset - /// of zero). - /// The example below, will calculate a checksum from the third byte in the block - /// for the length of data. If the block to read from offset 3 can be done in one - /// operation, it will do so. Else it must be done in two operations, first from offset - /// 3 to the end, then from offset 0 for the remaining data. + /// Similar to the property ReadLength, this function takes an argument offset which is used to + /// determine the length of data that can be read from that offset, until either the end of the block, or the + /// end of the buffer. + /// + /// This function is useful if you want to read a block of data, not starting from the offset 0 (and you don't + /// want to consume the data before hand to reach an offset of zero). + /// + /// + /// The example below, will calculate a checksum from the third byte in the block for the length of data. If the + /// block to read from offset 3 can be done in one operation, it will do so. Else it must be done in two + /// operations, first from offset 3 to the end, then from offset 0 for the remaining data. + /// /// /// - /// UInt16 crc; - /// if (buffer.GetReadBlock(3) >= length - 3) { - /// crc = crc16.Compute(buffer.Array, buffer.ToArrayIndex(3), length - 3); - /// } else { - /// crc = crc16.Compute(buffer.Array, buffer.ToArrayIndex(3), buffer.ReadLength - 3); - /// crc = crc16.Compute(crc, buffer.Array, 0, length - buffer.ReadLength); - /// } + /// short crc; if (buffer.GetReadBlock(3) >= length - 3) { crc = crc16.Compute(buffer.Array, + /// buffer.ToArrayIndex(3), length - 3); } else { crc = crc16.Compute(buffer.Array, buffer.ToArrayIndex(3), + /// buffer.ReadLength - 3); crc = crc16.Compute(crc, buffer.Array, 0, length - buffer.ReadLength); } /// - /// Offset. - /// Length. public int GetReadBlock(int offset) { - if (offset < 0) throw new ArgumentOutOfRangeException("offset", "offset must be zero or greater"); + if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "may not be negative"); if (offset >= m_Count) return 0; int s = (m_Start + offset) % m_Array.Length; @@ -272,25 +287,24 @@ public int GetReadBlock(int offset) /// /// Consume array elements (freeing space from the beginning) updating pointers in the circular buffer. /// + /// Amount of data to consume. + /// + /// is negative, or cannot consume more data than exists. + /// /// - /// This method advances the internal pointers for Start based on the length - /// that should be consumed. The pointer End does not change. It is important that - /// this method does not Reset() the buffer in case that all data is consumed. A - /// common scenario with Streams is to write into the buffer using asynchronous I/O. If a - /// Reset() occurs during an asynchronous I/O ReadFile(), the End - /// pointer is also changed, so that when a Produce() occurs on completion of the - /// ReadFile() operation, the pointers are updated, but not using the pointers - /// before the Reset(). No crash would occur (so long as the underlying array is - /// pinned), but data corruption would occur if this method were not used in this particular - /// scenario. + /// This method advances the internal pointers for Start based on the length that should be + /// consumed. The pointer End does not change. It is important that this method does not Reset() + /// the buffer in case that all data is consumed. A common scenario with Streams is to write into the buffer + /// using asynchronous I/O. If a Reset() occurs during an asynchronous I/O ReadFile(), the + /// End pointer is also changed, so that when a Produce() occurs on completion of the + /// ReadFile() operation, the pointers are updated, but not using the pointers before the Reset(). + /// No crash would occur (so long as the underlying array is pinned), but data corruption would occur if this + /// method were not used in this particular scenario. /// - /// Amount of data to consume. public void Consume(int length) { - if (length < 0) throw new ArgumentOutOfRangeException("length", "must be positive"); - if (length > m_Count) - throw new ArgumentOutOfRangeException("Can't consume more data than exists: Length=" + - m_Count + "; Consume=" + length); + if (length < 0 || length > m_Count) + throw new ArgumentOutOfRangeException(nameof(length), "Cannot consume negative length, or more data than exists"); // Note, some implementations may rely on the pointers being correctly advanced also in // the case that data is consumed. @@ -301,34 +315,43 @@ public void Consume(int length) /// /// Produce bytes (allocating space at the end) updating pointers in the circular buffer. /// - /// The number of bytes to indicate that have been added from the - /// index to the end of the array and possibly again from the start - /// of the array if overlapped. + /// + /// The number of bytes to indicate that have been added from the index to the end of the + /// array and possibly again from the start of the array if overlapped. + /// + /// + /// Cannot produce negative , or producing exceeds + /// . + /// public void Produce(int length) { - if (length < 0) throw new ArgumentOutOfRangeException("length", "must be positive"); - if (m_Count + length > m_Array.Length) - throw new ArgumentOutOfRangeException("length", "Can't produce more data than buffer size: Free=" + - Free + "; Produce=" + length); + if (length < 0 || length > m_Array.Length - m_Count) + throw new ArgumentOutOfRangeException(nameof(length), "Cannot produce negative length, or exceed buffer free"); + m_Count += length; } /// /// Revert elements produced to the end of the circular buffer. /// - /// The number of bytes to remove from the end of the array, moving - /// the property to the left, leaving the property - /// untouched. + /// + /// The number of bytes to remove from the end of the array, moving the property to the left, + /// leaving the property untouched. + /// + /// + /// The must be positive and not exceed the number of elements in the circular buffer. + /// /// - /// This method can be used to remove data that has been added to the end of the circular - /// buffer. When using this data structure for streams, you would not use this property - /// to ensure consistency of your stream (the Read operation would consume from - /// your circular buffer and Write would produce data to your circular buffer. + /// This method can be used to remove data that has been added to the end of the circular buffer. When using + /// this data structure for streams, you would not use this property to ensure consistency of your stream (your + /// Read operation would consume from your circular buffer and Write would produce data to your + /// circular buffer. /// public void Revert(int length) { - if (length < 0) throw new ArgumentOutOfRangeException("length", "must be positive"); - if (m_Count < length) throw new ArgumentOutOfRangeException("length", "must be less than number of elements in the circular buffer"); + if (length < 0 || length >= m_Count) + throw new ArgumentOutOfRangeException(nameof(length), + "must be positive and not exceed the number of elements in the circular buffer"); m_Count -= length; } @@ -346,22 +369,33 @@ public void Reset() /// Get the reference to the array that's allocated. /// /// - /// This property allows you to access the content of the data in the circular buffer in an - /// efficient manner. You can then use this property along with , - /// , and for knowing - /// where in the buffer to read and write. + /// This property allows you to access the content of the data in the circular buffer in an efficient manner. + /// You can then use this property along with , , + /// and for knowing where in the buffer to read and write. /// public T[] Array { get { return m_Array; } } /// /// Access an element in the array using the Start as index 0. /// - /// Index into the array referenced from Start. + /// Index into the array referenced from . /// Contents of the array. public T this[int index] { - get { return m_Array[(m_Start + index) % m_Array.Length]; } - set { m_Array[(m_Start + index) % m_Array.Length] = value; } + get + { +#if DEBUG + if (index >= Length) throw new ArgumentOutOfRangeException(nameof(index), "Index exceeded Buffer Length"); +#endif + return m_Array[(m_Start + index) % m_Array.Length]; + } + set + { +#if DEBUG + if (index >= Length) throw new ArgumentOutOfRangeException(nameof(index), "Index exceeded Buffer Length"); +#endif + m_Array[(m_Start + index) % m_Array.Length] = value; + } } /// @@ -369,16 +403,17 @@ public T this[int index] /// /// Array to copy from. /// Number of bytes copied. + /// + /// may not be . + /// /// - /// Data is copied to the end of the Circular Buffer. The amount of data - /// that could be copied is dependent on the amount of free space. The result - /// is the number of elements from the buffer array that is copied - /// into the Circular Buffer. Pointers in the circular buffer are updated - /// appropriately. + /// Data is copied to the end of the Circular Buffer. The amount of data that could be copied is dependent on + /// the amount of free space. The result is the number of elements from the buffer array that is copied + /// into the Circular Buffer. Pointers in the circular buffer are updated appropriately. /// public int Append(T[] array) { - if (array == null) throw new ArgumentNullException("array"); + if (array == null) throw new ArgumentNullException(nameof(array)); return Append(array, 0, array.Length); } @@ -389,19 +424,26 @@ public int Append(T[] array) /// Offset to copy data from. /// Length of data to copy. /// Number of bytes copied. + /// + /// may not be . + /// + /// + /// or may not be negative. + /// + /// + /// and exceed boundaries. + /// /// - /// Data is copied to the end of the Circular Buffer. The amount of data - /// that could be copied is dependent on the amount of free space. The result - /// is the number of elements from the buffer array that is copied - /// into the Circular Buffer. Pointers in the circular buffer are updated - /// appropriately. + /// Data is copied to the end of the Circular Buffer. The amount of data that could be copied is dependent on + /// the amount of free space. The result is the number of elements from the buffer array that is copied + /// into the Circular Buffer. Pointers in the circular buffer are updated appropriately. /// public int Append(T[] array, int offset, int count) { - if (array == null) throw new ArgumentNullException("array"); - if (offset < 0) throw new ArgumentOutOfRangeException("offset", "Offset must be positive"); - if (count < 0) throw new ArgumentOutOfRangeException("count", "Count must be positive"); - if (offset + count > array.Length) throw new ArgumentException("Parameters exceed array boundary"); + if (array == null) throw new ArgumentNullException(nameof(array)); + if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "may not be negative"); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), "may not be negative"); + if (offset > array.Length - count) throw new ArgumentException("Parameters exceed array boundary"); if (m_Count == Capacity) return 0; if (count == 0) return 0; @@ -423,16 +465,16 @@ public int Append(T[] array, int offset, int count) /// /// Buffer to append. /// Amount of data appended. + /// + /// may not be . + /// /// - /// Data is copied to the end of the Circular Buffer. The amount of data - /// that could be copied is dependent on the amount of free space. The result - /// is the number of elements from the buffer array that is copied - /// into the Circular Buffer. Pointers in the circular buffer are updated - /// appropriately. + /// Data is copied to the end of the Circular Buffer. The amount of data that could be copied is dependent on + /// the amount of free space. The result is the number of elements from the buffer array that is copied + /// into the Circular Buffer. Pointers in the circular buffer are updated appropriately. /// public int Append(CircularBuffer buffer) { - if (buffer == null) throw new ArgumentNullException("buffer"); return Append(buffer, 0, buffer.Length); } @@ -442,12 +484,17 @@ public int Append(CircularBuffer buffer) /// Buffer to append. /// Number of bytes to append. /// Amount of data appended. + /// + /// may not be . + /// + /// + /// would exceed boundaries of . + /// + /// may not be negative. /// - /// Data is copied to the end of the Circular Buffer. The amount of data - /// that could be copied is dependent on the amount of free space. The result - /// is the number of elements from the buffer array that is copied - /// into the Circular Buffer. Pointers in the circular buffer are updated - /// appropriately. + /// Data is copied to the end of the Circular Buffer. The amount of data that could be copied is dependent on + /// the amount of free space. The result is the number of elements from the buffer array that is copied + /// into the Circular Buffer. Pointers in the circular buffer are updated appropriately. /// public int Append(CircularBuffer buffer, int count) { @@ -461,19 +508,28 @@ public int Append(CircularBuffer buffer, int count) /// Number of bytes to append. /// Offset into the buffer to start appending. /// Amount of data appended. + /// + /// may not be . + /// + /// + /// may not be negative; + /// - or - + /// may not be negative. + /// + /// + /// and would exceed boundaries of . + /// /// - /// Data is copied to the end of the Circular Buffer. The amount of data - /// that could be copied is dependent on the amount of free space. The result - /// is the number of elements from the buffer array that is copied - /// into the Circular Buffer. Pointers in the circular buffer are updated - /// appropriately. + /// Data is copied to the end of the Circular Buffer. The amount of data that could be copied is dependent on + /// the amount of free space. The result is the number of elements from the buffer array that is copied + /// into the Circular Buffer. Pointers in the circular buffer are updated appropriately. /// public int Append(CircularBuffer buffer, int offset, int count) { - if (buffer == null) throw new ArgumentNullException("buffer"); - if (offset < 0) throw new ArgumentOutOfRangeException("offset", "Offset must be positive"); - if (count < 0) throw new ArgumentOutOfRangeException("count", "Count must be positive"); - if (offset + count > buffer.Length) throw new ArgumentException("Parameters exceed buffer boundary"); + if (buffer == null) throw new ArgumentNullException(nameof(buffer)); + if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "may not be negative"); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), "may not be negative"); + if (offset > buffer.Length - count) throw new ArgumentException("Parameters exceed buffer boundary"); if (m_Count == Capacity) return 0; if (count == 0) return 0; @@ -502,7 +558,7 @@ public int Append(T element) { if (m_Count == Capacity) return 0; - m_Array[this.End] = element; + m_Array[End] = element; Produce(1); return 1; } @@ -511,6 +567,7 @@ public int Append(T element) /// Retrieve a single element from the Circular buffer and consume it. /// /// The value at index 0. + /// Circular buffer is empty. public T Pop() { if (m_Count == 0) throw new InvalidOperationException("Circular Buffer is empty"); @@ -522,12 +579,12 @@ public T Pop() /// /// Copy data from the circular buffer to the array and then consume the data from the circular buffer. /// - /// - /// Data is copied to the first element in the array, up to the length - /// of the array. - /// /// The array to copy the data to. /// The number of bytes that were moved. + /// + /// may not be . + /// + /// Data is copied to the first element in the array, up to the length of the array. public int MoveTo(T[] array) { int l = CopyTo(array); @@ -542,9 +599,20 @@ public int MoveTo(T[] array) /// Offset into the array to copy to. /// Amount of data to copy to. /// The number of bytes that were moved. + /// + /// may not be . + /// + /// + /// may not be negative; + /// - or - + /// may not be negative. + /// + /// + /// and would exceed length. + /// /// - /// This method is very similar to the method, but it will also - /// consume the data that was copied also. + /// This method is very similar to the method, but it will also consume the + /// data that was copied also. /// public int MoveTo(T[] array, int offset, int count) { @@ -558,14 +626,16 @@ public int MoveTo(T[] array, int offset, int count) /// /// The array to copy the data to. /// The number of bytes that were copied. + /// + /// may not be . + /// /// - /// Data is copied from the first element in the array, up to the length - /// of the array. The data from the Circular Buffer is not consumed. - /// You must do this yourself. Else use the MoveTo() method. + /// Data is copied from the first element in the array, up to the length of the array. The data from the + /// Circular Buffer is not consumed. You must do this yourself. Else use the MoveTo() method. /// public int CopyTo(T[] array) { - if (array == null) throw new ArgumentNullException("array"); + if (array == null) throw new ArgumentNullException(nameof(array)); return CopyTo(array, 0, array.Length); } @@ -576,18 +646,28 @@ public int CopyTo(T[] array) /// Offset into the array to copy to. /// Amount of data to copy to. /// The number of bytes that were copied. + /// + /// may not be . + /// + /// + /// may not be negative; + /// - or - + /// may not be negative. + /// + /// + /// and would exceed length. + /// /// - /// Data is copied from the circular buffer into the array specified, at the offset given. - /// The data from the Circular Buffer is not consumed. You must do this yourself. - /// Else use the MoveTo() method. + /// Data is copied from the circular buffer into the array specified, at the offset given. The data from the + /// Circular Buffer is not consumed. You must do this yourself. Else use the MoveTo() method. /// public int CopyTo(T[] array, int offset, int count) { - if (array == null) throw new ArgumentNullException("array"); + if (array == null) throw new ArgumentNullException(nameof(array)); if (count == 0) return 0; - if (offset < 0) throw new ArgumentOutOfRangeException("offset", "Offset must be positive"); - if (count < 0) throw new ArgumentOutOfRangeException("count", "Count must be positive"); - if (array.Length < offset + count) throw new ArgumentException("Offset and count exceed boundary length"); + if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "may not be negative"); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), "may not be negative"); + if (offset > array.Length - count) throw new ArgumentException("Offset and count exceed boundary length"); int length = Math.Min(count, Length); if (ReadLength >= count) { @@ -613,9 +693,7 @@ internal static class CircularBufferExtensions /// /// The circular buffer based on char. /// A string containing the contents of the circular buffer. - /// - /// This method will not consume the data in the CircularBuffer{char}. - /// + /// This method will not consume the data in the CircularBuffer{char}. public static string GetString(this CircularBuffer buff) { if (buff == null) return null; @@ -628,9 +706,7 @@ public static string GetString(this CircularBuffer buff) /// The circular buffer based on char. /// Number of characters to convert to a string. /// A string of up to length characters. - /// - /// This method will not consume the data in the CircularBuffer{char}. - /// + /// This method will not consume the data in the CircularBuffer{char}. public static string GetString(this CircularBuffer buff, int length) { if (buff == null) return null; @@ -651,10 +727,10 @@ public static string GetString(this CircularBuffer buff, int length) /// The circular buffer based on char. /// The offset into the circular buffer. /// Number of characters to convert to a string. - /// A string of up to length characters, from the circular buffer starting at the offset specified.. - /// - /// This method will not consume the data in the CircularBuffer{char}. - /// + /// + /// A string of up to length characters, from the circular buffer starting at the offset specified.. + /// + /// This method will not consume the data in the CircularBuffer{char}. public static string GetString(this CircularBuffer buff, int offset, int length) { if (buff == null) return null; @@ -681,70 +757,94 @@ public static string GetString(this CircularBuffer buff, int offset, int l /// An array to store the converted characters. /// The first element of chars in which data is stored. /// Maximum number of characters to write. - /// true to indicate that no further data is to be converted; otherwise, false. - /// When this method returns, contains the number of bytes that were - /// used in the conversion. This parameter is passed uninitialized. - /// When this method returns, contains the number of characters from - /// chars that were produced by the conversion. This parameter is passed uninitialized. - /// When this method returns, contains true if all the characters - /// specified by byteCount were converted; otherwise, false. This parameter is - /// passed uninitialized. - /// The output buffer is too small to contain any of the - /// converted input. + /// + /// to indicate that no further data is to be converted; otherwise, + /// . + /// + /// + /// When this method returns, contains the number of bytes that were used in the conversion. This parameter is + /// passed uninitialized. + /// + /// + /// When this method returns, contains the number of characters from chars that were produced by the conversion. + /// This parameter is passed uninitialized. + /// + /// + /// When this method returns, contains if all the characters specified by byteCount were + /// converted; otherwise, . This parameter is passed uninitialized. + /// + /// + /// The output buffer is too small to contain any of the converted input. + /// + /// + /// or may not be . + /// + /// + /// may not be negative; + /// - or - + /// may not be negative. + /// /// /// This method should behave the same as the decoder for an array of bytes of equal size. - /// The completed output parameter indicates whether all the data in the input buffer - /// was converted and stored in the output buffer. This parameter is set to false if the - /// number of bytes specified by the bytes.Length parameter cannot be converted without - /// exceeding the number of characters specified by the charCount parameter. - /// The completed parameter can also be set to false, even though the all bytes were consumed. - /// This situation occurs if there is still data in the Decoder object that has not been stored - /// in the bytes buffer. - /// There are a few noted deviations from using the Decoder on an array of bytes, instead - /// of a Circular Buffer. + /// + /// The completed output parameter indicates whether all the data in the input buffer was converted and + /// stored in the output buffer. This parameter is set to if the number of bytes + /// specified by the bytes.Length parameter cannot be converted without exceeding the number of + /// characters specified by the charCount parameter. + /// + /// + /// The completed parameter can also be set to , even though the all bytes were consumed. + /// This situation occurs if there is still data in the Decoder object that has not been stored in the bytes + /// buffer. + /// + /// + /// There are a few noted deviations from using the Decoder on an array of bytes, instead of a Circular Buffer. + /// /// - /// When converting a sequence of bytes to multiple chars, if those sequences result in - /// the minimum number of characters being written as 2 or more characters, slight discrepancies - /// occur. A UTF8 decoder would convert the sequence F3 A0 82 84 to the two characters DB40 DC84. - /// The UTF8 decoder would not consume any of the 4 bytes if all 4 bytes are immediately - /// available to a single call to the Decoder.Convert() function and instead raise an exception. - /// This Convert() function may consume some of these bytes and indicate success, if the byte - /// sequence wraps over from the end of the array to the beginning of the array. The number of - /// bytes consumed (bytesUsed) is correct and characters produced (charsUsed) is also correct. - /// There is no error found according to the MS documentation. The next call will result in - /// an exception instead. So this function may: consume more bytes than expected (but with the - /// correct results); and may not raise an exception immediately if those bytes were consumed. + /// + /// When converting a sequence of bytes to multiple chars, if those sequences result in the minimum number of + /// characters being written as 2 or more characters, slight discrepancies occur. A UTF8 decoder would convert + /// the sequence F3 A0 82 84 to the two characters DB40 DC84. The UTF8 decoder would not consume any of the 4 + /// bytes if all 4 bytes are immediately available to a single call to the Decoder.Convert() function and + /// instead raise an exception. This Convert() function may consume some of these bytes and indicate success, if + /// the byte sequence wraps over from the end of the array to the beginning of the array. The number of bytes + /// consumed (bytesUsed) is correct and characters produced (charsUsed) is also correct. There is no error found + /// according to the MS documentation. The next call will result in an exception instead. So this function may: + /// consume more bytes than expected (but with the correct results); and may not raise an exception immediately + /// if those bytes were consumed. + /// /// /// public static void Convert(this Decoder decoder, CircularBuffer bytes, char[] chars, int charIndex, int charCount, bool flush, out int bytesUsed, out int charsUsed, out bool completed) { - if (bytes == null) throw new ArgumentNullException("bytes", "Circular buffer bytes may not be null"); - if (chars == null) throw new ArgumentNullException("chars", "Array chars may not be null"); - if (charIndex < 0) throw new ArgumentOutOfRangeException("charIndex", "Negative offset provided"); - if (charCount < 0) throw new ArgumentOutOfRangeException("charCount", "Negative count provided"); + if (bytes == null) throw new ArgumentNullException(nameof(bytes)); + if (chars == null) throw new ArgumentNullException(nameof(chars)); + if (charIndex < 0) throw new ArgumentOutOfRangeException(nameof(charIndex), "may not be negative"); + if (charCount < 0) throw new ArgumentOutOfRangeException(nameof(charCount), "may not be negative"); if (chars.Length - charIndex < charCount) throw new ArgumentException("charIndex and charCount exceed char buffer boundaries"); bytesUsed = 0; charsUsed = 0; + completed = true; bool outFlush = false; - do { + int rl = bytes.ReadLength; + while (rl > 0 && charCount > 0) { int bu; int cu; - if (bytes.ReadLength == bytes.Length) outFlush = flush; + if (rl == bytes.Length) outFlush = flush; try { - decoder.Convert(bytes.Array, bytes.Start, bytes.ReadLength, + decoder.Convert(bytes.Array, bytes.Start, rl, chars, charIndex, charCount, outFlush, out bu, out cu, out completed); - } catch (System.ArgumentException e) { - if (!e.ParamName.Equals("chars")) throw; + } catch (ArgumentException e) { + if (e.ParamName == null || !e.ParamName.Equals("chars")) throw; - // NOTE: While a decoder may not consume anything, using the CircularBuffer - // extension may, if the bytes need to be passed to the decoder twice. This is - // because we can't know what bytes may cause the error. The same kind of behaviour - // would occur if you feed one byte at a time to the decoder yourself. It will - // be passed twice if the byte sequence is split between the end and the start - // of the circular queue. + // NOTE: While a decoder may not consume anything, using the CircularBuffer extension may, if the + // bytes need to be passed to the decoder twice. This is because we can't know what bytes may cause + // the error. The same kind of behavior would occur if you feed one byte at a time to the decoder + // yourself. It will be passed twice if the byte sequence is split between the end and the start of + // the circular queue. if (bytesUsed == 0) throw; completed = false; @@ -755,7 +855,8 @@ public static void Convert(this Decoder decoder, CircularBuffer bytes, cha charCount -= cu; charsUsed += cu; charIndex += cu; - } while (bytes.ReadLength > 0 && charCount > 0); + rl = bytes.ReadLength; + } } /// @@ -765,42 +866,57 @@ public static void Convert(this Decoder decoder, CircularBuffer bytes, cha /// The circular buffer of bytes to convert from. /// The circular buffer of chars to convert to. /// Maximum number of characters to write. - /// true to indicate that no further data is to be converted; otherwise, false. - /// When this method returns, contains the number of bytes that were - /// used in the conversion. This parameter is passed uninitialized. - /// When this method returns, contains the number of characters from - /// chars that were produced by the conversion. This parameter is passed uninitialized. - /// When this method returns, contains true if all the characters - /// specified by byteCount were converted; otherwise, false. This parameter is - /// passed uninitialized. - /// The output buffer is too small to contain any of the - /// converted input. + /// + /// to indicate that no further data is to be converted; otherwise, + /// . + /// + /// + /// When this method returns, contains the number of bytes that were used in the conversion. This parameter is + /// passed uninitialized. + /// + /// + /// When this method returns, contains the number of characters from chars that were produced by the conversion. + /// This parameter is passed uninitialized. + /// + /// + /// When this method returns, contains if all the characters specified by byteCount were + /// converted; otherwise, . This parameter is passed uninitialized. + /// + /// + /// The output buffer is too small to contain any of the converted input. + /// + /// + /// or may not be . + /// + /// may not be negative. public static void Convert(this Decoder decoder, CircularBuffer bytes, CircularBuffer chars, int charCount, bool flush, out int bytesUsed, out int charsUsed, out bool completed) { - if (bytes == null) throw new ArgumentNullException("bytes", "Circular buffer bytes may not be null"); - if (chars == null) throw new ArgumentNullException("chars", "Circular buffer chars may not be null"); + if (bytes == null) throw new ArgumentNullException(nameof(bytes)); + if (chars == null) throw new ArgumentNullException(nameof(chars)); charCount = Math.Min(chars.Free, charCount); bytesUsed = 0; charsUsed = 0; + completed = true; bool outFlush = false; - do { + int rl = bytes.ReadLength; + while (rl > 0 && charCount > 0) { int bu; int cu; - if (bytes.ReadLength == bytes.Length) outFlush = flush; + if (rl == bytes.Length) outFlush = flush; try { - decoder.Convert(bytes.Array, bytes.Start, bytes.ReadLength, + decoder.Convert(bytes.Array, bytes.Start, rl, chars.Array, chars.End, Math.Min(chars.WriteLength, charCount), outFlush, out bu, out cu, out completed); bytes.Consume(bu); chars.Produce(cu); - } catch (System.ArgumentException e) { - if (!e.ParamName.Equals("chars")) throw; + rl = bytes.ReadLength; + } catch (ArgumentException e) { + if (e.ParamName == null || !e.ParamName.Equals("chars")) throw; - // Decoder tried to write bytes, but not enough free space. We need to write to a temp - // array, then copy into the circular buffer. We assume that the underlying decoder - // hasn't changed state. + // Decoder tried to write bytes, but not enough free space. We need to write to a temp array, then + // copy into the circular buffer. We assume that the underlying decoder hasn't changed state. if (charCount <= chars.WriteLength) { // There's no free space left, so we raise the same exception as the decoder if (bytesUsed == 0) throw; @@ -811,21 +927,22 @@ public static void Convert(this Decoder decoder, CircularBuffer bytes, Cir int tmpLen = Math.Min(16, charCount); char[] tmp = new char[tmpLen]; try { - decoder.Convert(bytes.Array, bytes.Start, bytes.ReadLength, + decoder.Convert(bytes.Array, bytes.Start, rl, tmp, 0, tmp.Length, outFlush, out bu, out cu, out completed); - } catch (System.ArgumentException e2) { - if (!e2.ParamName.Equals("chars")) throw; + } catch (ArgumentException e2) { + if (e2.ParamName == null || !e2.ParamName.Equals("chars")) throw; if (bytesUsed == 0) throw; completed = false; return; } bytes.Consume(bu); chars.Append(tmp, 0, cu); + rl = bytes.ReadLength; } bytesUsed += bu; charCount -= cu; charsUsed += cu; - } while (bytes.ReadLength > 0 && charCount > 0); + } } /// @@ -834,20 +951,32 @@ public static void Convert(this Decoder decoder, CircularBuffer bytes, Cir /// The decoder to do the conversion. /// The circular buffer of bytes to convert from. /// The circular buffer of chars to convert to. - /// true to indicate that no further data is to be converted; otherwise, false. - /// When this method returns, contains the number of bytes that were - /// used in the conversion. This parameter is passed uninitialized. - /// When this method returns, contains the number of characters from - /// chars that were produced by the conversion. This parameter is passed uninitialized. - /// When this method returns, contains true if all the characters - /// specified by byteCount were converted; otherwise, false. This parameter is - /// passed uninitialized. - /// The output buffer is too small to contain any of the - /// converted input. + /// + /// to indicate that no further data is to be converted; otherwise, + /// . + /// + /// + /// When this method returns, contains the number of bytes that were used in the conversion. This parameter is + /// passed uninitialized. + /// + /// + /// When this method returns, contains the number of characters from chars that were produced by the conversion. + /// This parameter is passed uninitialized. + /// + /// + /// When this method returns, contains if all the characters specified by byteCount were + /// converted; otherwise, . This parameter is passed uninitialized. + /// + /// + /// The output buffer is too small to contain any of the converted input. + /// + /// + /// or may not be . + /// public static void Convert(this Decoder decoder, CircularBuffer bytes, CircularBuffer chars, bool flush, out int bytesUsed, out int charsUsed, out bool completed) { - if (bytes == null) throw new ArgumentNullException("bytes", "Circular buffer bytes may not be null"); - if (chars == null) throw new ArgumentNullException("chars", "Circular buffer chars may not be null"); + if (bytes == null) throw new ArgumentNullException(nameof(bytes)); + if (chars == null) throw new ArgumentNullException(nameof(chars)); decoder.Convert(bytes, chars, chars.Free, flush, out bytesUsed, out charsUsed, out completed); } @@ -859,29 +988,54 @@ public static void Convert(this Decoder decoder, CircularBuffer bytes, Cir /// Start index in bytes array. /// Number of bytes to convert in the byte array. /// The circular buffer of chars to convert to. - /// true to indicate that no further data is to be converted; otherwise, false. - /// When this method returns, contains the number of bytes that were - /// used in the conversion. This parameter is passed uninitialized. - /// When this method returns, contains the number of characters from - /// chars that were produced by the conversion. This parameter is passed uninitialized. - /// When this method returns, contains true if all the characters - /// specified by byteCount were converted; otherwise, false. This parameter is - /// passed uninitialized. + /// + /// to indicate that no further data is to be converted; otherwise, + /// . + /// + /// + /// When this method returns, contains the number of bytes that were used in the conversion. This parameter is + /// passed uninitialized. + /// + /// + /// When this method returns, contains the number of characters from chars that were produced by the conversion. + /// This parameter is passed uninitialized. + /// + /// + /// When this method returns, contains if all the characters specified by byteCount were + /// converted; otherwise, . This parameter is passed uninitialized. + /// + /// + /// The output buffer is too small to contain any of the converted input. + /// + /// + /// or may not be . + /// + /// + /// may not be negative; + /// - or - + /// may not be negative. + /// public static void Convert(this Decoder decoder, byte[] bytes, int byteIndex, int byteCount, CircularBuffer chars, bool flush, out int bytesUsed, out int charsUsed, out bool completed) { - if (bytes == null) throw new ArgumentNullException("bytes", "Array bytes may not be null"); - if (chars == null) throw new ArgumentNullException("chars", "CircularBuffer chars may not be null"); - if (byteIndex < 0) throw new ArgumentOutOfRangeException("byteIndex", "Negative offset provided"); - if (byteCount < 0) throw new ArgumentOutOfRangeException("byteCount", "Negative count provided"); + if (bytes == null) throw new ArgumentNullException(nameof(bytes)); + if (chars == null) throw new ArgumentNullException(nameof(chars)); + if (byteIndex < 0) throw new ArgumentOutOfRangeException(nameof(byteIndex), "may not be negative"); + if (byteCount < 0) throw new ArgumentOutOfRangeException(nameof(byteCount), "may not be negative"); if (bytes.Length - byteIndex < byteCount) throw new ArgumentException("byteIndex and byteCount exceed byte buffer boundaries"); bytesUsed = 0; charsUsed = 0; + completed = true; + if (byteCount == 0) return; do { int bu; int cu; try { + if (bytesUsed != 0 && chars.WriteLength == 0) { + completed = false; + return; + } decoder.Convert(bytes, byteIndex, byteCount, chars.Array, chars.End, chars.WriteLength, flush, out bu, out cu, out completed); @@ -890,12 +1044,11 @@ public static void Convert(this Decoder decoder, byte[] bytes, int byteIndex, in byteIndex += bu; chars.Produce(cu); charsUsed += cu; - } catch (System.ArgumentException e) { - if (!e.ParamName.Equals("chars")) throw; + } catch (ArgumentException e) { + if (e.ParamName == null || !e.ParamName.Equals("chars")) throw; - // Decoder tried to write bytes, but not enough free space. We need to write to a temp - // array, then copy into the circular buffer. We assume that the underlying decoder - // hasn't changed state. + // Decoder tried to write bytes, but not enough free space. We need to write to a temp array, then + // copy into the circular buffer. We assume that the underlying decoder hasn't changed state. if (chars.WriteLength == chars.Free) { // There's no free space left, so we raise the same exception as the decoder if (bytesUsed == 0) throw; @@ -908,9 +1061,9 @@ public static void Convert(this Decoder decoder, byte[] bytes, int byteIndex, in try { decoder.Convert(bytes, byteIndex, byteCount, tmp, 0, tmp.Length, flush, out bu, out cu, out completed); - } catch (System.ArgumentException e2) { + } catch (ArgumentException e2) { // There still isn't enough space, so abort - if (!e2.ParamName.Equals("chars")) throw; + if (e2.ParamName == null || !e2.ParamName.Equals("chars")) throw; if (bytesUsed == 0) throw; completed = false; return; @@ -932,32 +1085,57 @@ public static void Convert(this Decoder decoder, byte[] bytes, int byteIndex, in /// The first element of chars to convert. /// The number of elements of chars to convert. /// Circular buffer where converted bytes are stored. - /// true to indicate no further data is to be converted; otherwise, false - /// When this method returns, contains the number of characters from - /// chars that were produced by the conversion. This parameter is passed uninitialized. - /// When this method returns, contains the number of bytes that were - /// used in the conversion. This parameter is passed uninitialized. - /// When this method returns, contains true if all the characters - /// specified by byteCount were converted; otherwise, false. This parameter is - /// passed uninitialized. + /// + /// to indicate no further data is to be converted; otherwise, + /// + /// + /// When this method returns, contains the number of characters from chars that were produced by the conversion. + /// This parameter is passed uninitialized. + /// + /// + /// When this method returns, contains the number of bytes that were used in the conversion. This parameter is + /// passed uninitialized. + /// + /// + /// When this method returns, contains if all the characters specified by byteCount were + /// converted; otherwise, . This parameter is passed uninitialized. + /// + /// + /// The output buffer is too small to contain any of the converted input. + /// + /// + /// or may not be . + /// + /// + /// may not be negative; + /// - or - + /// may not be negative. + /// public static void Convert(this Encoder encoder, char[] chars, int charIndex, int charCount, CircularBuffer bytes, bool flush, out int charsUsed, out int bytesUsed, out bool completed) { - // The code here is the same as the "Decoder" version as they do the same thing. Unfortunately, - // .NET doesn't have a base class for this, so we need two separate encoder/decoder methods. + // The code here is the same as the "Decoder" version as they do the same thing. Unfortunately, .NET doesn't + // have a base class for this, so we need two separate encoder/decoder methods. - if (chars == null) throw new ArgumentNullException("chars", "chars may not be null"); - if (bytes == null) throw new ArgumentNullException("bytes", "Circular buffer bytes may not be null"); - if (charIndex < 0) throw new ArgumentOutOfRangeException("charIndex", "Negative offset provided"); - if (charCount < 0) throw new ArgumentOutOfRangeException("charCount", "Negative count provided"); + if (chars == null) throw new ArgumentNullException(nameof(chars)); + if (bytes == null) throw new ArgumentNullException(nameof(bytes)); + if (charIndex < 0) throw new ArgumentOutOfRangeException(nameof(charIndex), "may not be negative"); + if (charCount < 0) throw new ArgumentOutOfRangeException(nameof(charCount), "may not be negative"); if (chars.Length - charIndex < charCount) throw new ArgumentException("charIndex and charCount exceed char buffer boundaries"); bytesUsed = 0; charsUsed = 0; + completed = true; + if (charCount == 0) return; do { int bu; int cu; try { + if (charsUsed != 0 && bytes.WriteLength == 0) { + completed = false; + return; + } + encoder.Convert(chars, charIndex, charCount, bytes.Array, bytes.End, bytes.WriteLength, flush, out cu, out bu, out completed); @@ -966,12 +1144,11 @@ public static void Convert(this Encoder encoder, char[] chars, int charIndex, in charIndex += cu; bytes.Produce(bu); bytesUsed += bu; - } catch (System.ArgumentException e) { - if (!e.ParamName.Equals("bytes")) throw; + } catch (ArgumentException e) { + if (e.ParamName == null || !e.ParamName.Equals("bytes")) throw; - // Encoder tried to write chars, but not enough free space. We need to write to a temp - // array, then copy into the circular buffer. We assume that the underlying encoder - // hasn't changed state. + // Encoder tried to write chars, but not enough free space. We need to write to a temp array, then + // copy into the circular buffer. We assume that the underlying encoder hasn't changed state. if (bytes.WriteLength == bytes.Free) { // There's no free space left, so we raise the same exception as the decoder if (charsUsed == 0) throw; @@ -984,9 +1161,9 @@ public static void Convert(this Encoder encoder, char[] chars, int charIndex, in try { encoder.Convert(chars, charIndex, charCount, tmp, 0, tmp.Length, flush, out cu, out bu, out completed); - } catch (System.ArgumentException e2) { + } catch (ArgumentException e2) { // There still isn't enough space, so abort - if (!e2.ParamName.Equals("bytes")) throw; + if (e2.ParamName == null || !e2.ParamName.Equals("bytes")) throw; if (charsUsed == 0) throw; completed = false; return; diff --git a/code/Properties/AssemblyInfo.cs b/code/Properties/AssemblyInfo.cs index c32e8638..bacd0876 100644 --- a/code/Properties/AssemblyInfo.cs +++ b/code/Properties/AssemblyInfo.cs @@ -43,5 +43,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.2.1.0")] -[assembly: AssemblyFileVersion("2.2.1.0")] +[assembly: AssemblyVersion("2.2.2.0")] +[assembly: AssemblyFileVersion("2.2.2.0")] diff --git a/code/SerialPortStream-net40.csproj b/code/SerialPortStream-net40.csproj index baada99d..ab7a8266 100644 --- a/code/SerialPortStream-net40.csproj +++ b/code/SerialPortStream-net40.csproj @@ -11,7 +11,7 @@ v4.0 512 - 2.2.1.0 + 2.2.2.0 true diff --git a/code/SerialPortStream-net45.csproj b/code/SerialPortStream-net45.csproj index 56726ef3..9c9787b6 100644 --- a/code/SerialPortStream-net45.csproj +++ b/code/SerialPortStream-net45.csproj @@ -11,7 +11,7 @@ v4.5 512 - 2.2.1.0 + 2.2.2.0 true diff --git a/code/SerialPortStream-netstandard15.csproj b/code/SerialPortStream-netstandard15.csproj index bfeafb7b..f382903e 100644 --- a/code/SerialPortStream-netstandard15.csproj +++ b/code/SerialPortStream-netstandard15.csproj @@ -3,7 +3,7 @@ An independent implementation of System.IO.Ports.SerialPort and SerialStream for better reliability and maintainability. SerialPortStream - 2.2.1.0 + 2.2.2.0 netstandard1.5 $(DefineConstants);NETSTANDARD15 true diff --git a/code/SerialPortStream.nuspec b/code/SerialPortStream.nuspec index 88c1c54b..fac20d85 100644 --- a/code/SerialPortStream.nuspec +++ b/code/SerialPortStream.nuspec @@ -2,7 +2,7 @@ SerialPortStream - 2.2.1.1 + 2.2.2 SerialPortStream Jason Curl Jason Curl diff --git a/test/DatastructuresTest/CircularBufferTest.cs b/test/DatastructuresTest/CircularBufferTest.cs index 7e35f1e9..614de1bf 100644 --- a/test/DatastructuresTest/CircularBufferTest.cs +++ b/test/DatastructuresTest/CircularBufferTest.cs @@ -8,9 +8,70 @@ namespace RJCP.Datastructures.CircularBufferTest using System.Text; using NUnit.Framework; - [TestFixture(Category = "Datastructures/CircularBuffer")] + [TestFixture(Category = "Datastructures.CircularBuffer")] public class CircularBufferTest { + [TestCase(-1)] + [TestCase(0)] + [TestCase(int.MinValue)] + public void CircularBuffer_InvalidCapacity(int capacity) + { + Assert.That(() => { _ = new CircularBuffer(capacity); }, Throws.TypeOf()); + } + + [Test] + public void CircularBuffer_NullArray() + { + Assert.That(() => { _ = new CircularBuffer(null); }, Throws.TypeOf()); + } + + [Test] + public void CircularBuffer_EmptyArray() + { + Assert.That(() => { _ = new CircularBuffer(new byte[0]); }, Throws.TypeOf()); + } + + [Test]public void CircularBuffer_NullArrayCount() + { + Assert.That(() => { _ = new CircularBuffer(null, 0); }, Throws.TypeOf()); + } + + [Test] + public void CircularBuffer_EmptyArrayCount() + { + Assert.That(() => { _ = new CircularBuffer(new byte[0], 1); }, Throws.TypeOf()); + } + + [Test] + public void CircularBuffer_CountOutOfBounds() + { + Assert.That(() => { _ = new CircularBuffer(new byte[10], 11); }, Throws.TypeOf()); + } + + [Test] + public void CircularBuffer_NullArrayCountOffset() + { + Assert.That(() => { _ = new CircularBuffer(null, 0, 0); }, Throws.TypeOf()); + } + + [Test] + public void CircularBuffer_EmptyArrayCountOffset() + { + Assert.That(() => { _ = new CircularBuffer(new byte[0], 0, 0); }, Throws.TypeOf()); + } + + [Test] + public void CircularBuffer_CountOutOfBoundsCountWithOffset() + { + Assert.That(() => { _ = new CircularBuffer(new byte[10], 0, 11); }, Throws.TypeOf()); + } + + [Test] + public void CircularBuffer_CountOutOfBoundsOffset() + { + Assert.That(() => { _ = new CircularBuffer(new byte[10], 11, 0); }, Throws.TypeOf()); + } + [Test] public void CircularBuffer_ProduceConsume() { @@ -460,6 +521,76 @@ public void CircularBuffer_ConstructorArray() Assert.That(cb3[0], Is.EqualTo(0x02)); } + [Test] + public void CircularBufferExt_DecoderConvertSourceLengthZeroDestCharArray() + { + // On Mono when using ISO-8859-15, a source length of zero causes problems. + Decoder d = Encoding.GetEncoding("ISO-8859-15").GetDecoder(); + CircularBuffer cb = new CircularBuffer(100); + char[] outc = new char[100]; + + d.Convert(cb, outc, 0, outc.Length, true, out int bu, out int cu, out bool completed); + Assert.That(bu, Is.EqualTo(0)); + Assert.That(cu, Is.EqualTo(0)); + Assert.That(completed, Is.True); + } + + [Test] + public void CircularBufferExt_DecoderConvertSourceLengthZeroDestCharBuffer() + { + // On Mono when using ISO-8859-15, a source length of zero causes problems. + Decoder d = Encoding.GetEncoding("ISO-8859-15").GetDecoder(); + CircularBuffer cb = new CircularBuffer(100); + CircularBuffer cc = new CircularBuffer(100); + + d.Convert(cb, cc, cc.Length, true, out int bu, out int cu, out bool completed); + Assert.That(bu, Is.EqualTo(0)); + Assert.That(cu, Is.EqualTo(0)); + Assert.That(completed, Is.True); + } + + [Test] + public void CircularBufferExt_DecoderConvertSourceLengthZeroDestCharBuffer2() + { + // On Mono when using ISO-8859-15, a source length of zero causes problems. + Decoder d = Encoding.GetEncoding("ISO-8859-15").GetDecoder(); + CircularBuffer cb = new CircularBuffer(100); + CircularBuffer cc = new CircularBuffer(100); + + d.Convert(cb, cc, true, out int bu, out int cu, out bool completed); + Assert.That(bu, Is.EqualTo(0)); + Assert.That(cu, Is.EqualTo(0)); + Assert.That(completed, Is.True); + } + + [Test] + public void CircularBufferExt_DecoderConvertSourceArrayZeroDestCharBuffer() + { + // On Mono when using ISO-8859-15, a source length of zero causes problems. + Decoder d = Encoding.GetEncoding("ISO-8859-15").GetDecoder(); + byte[] sb = new byte[100]; + CircularBuffer cc = new CircularBuffer(100); + + d.Convert(sb, 0, 0, cc, true, out int bu, out int cu, out bool completed); + Assert.That(bu, Is.EqualTo(0)); + Assert.That(cu, Is.EqualTo(0)); + Assert.That(completed, Is.True); + } + + [Test] + public void CircularBufferExt_EncoderConvertSourceZero() + { + // On Mono when using ISO-8859-15, a source length of zero causes problems. + Encoder e = Encoding.GetEncoding("ISO-8859-15").GetEncoder(); + char[] sc = new char[100]; + CircularBuffer cb = new CircularBuffer(100); + + e.Convert(sc, 0, 0, cb, true, out int cu, out int bu, out bool completed); + Assert.That(cu, Is.EqualTo(0)); + Assert.That(bu, Is.EqualTo(0)); + Assert.That(completed, Is.True); + } + /// /// Check converting a byte array to a char array with convert works. /// @@ -692,7 +823,6 @@ public void CircularBufferExt_DecoderConvert1_Utf16Chars1() [Test] public void CircularBufferExt_DecoderConvert1_Utf16Chars2() { - Decoder d = Encoding.UTF8.GetDecoder(); byte[] m = { 0x82, 0x84, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, @@ -703,6 +833,7 @@ public void CircularBufferExt_DecoderConvert1_Utf16Chars2() CircularBuffer cb = new CircularBuffer(m, 16, 16); // Based on the test "Decoder_Utf16Chars2" + Decoder d = Encoding.UTF8.GetDecoder(); d.Convert(cb, c, 0, 13, false, out _, out int cu, out bool complete); Assert.That(complete, Is.False); Assert.That(cu, Is.EqualTo(12)); @@ -711,66 +842,82 @@ public void CircularBufferExt_DecoderConvert1_Utf16Chars2() // This particular test is hard. The decoder consumes 12 bytes, but our function consumes more (because the // 4-bytes cross a boundary). The decoder needs to see all four bytes to decide not to convert it. There is // nothing in the documentation to say that the decoder should behave this way. So we can't simulate the - // original behaviour in this case. + // original behavior in this case. } [Test] public void CircularBufferExt_DecoderConvert1_Utf16Chars3() { - Decoder d = Encoding.UTF8.GetDecoder(); byte[] m = { 0x82, 0x84, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0xF3, 0xA0 }; - char[] c = new char[30]; - CircularBuffer cb = new CircularBuffer(m, 28, 4); // Based on the test "Decoder_Utf16Chars3" - // The behaviour isn't the same due to non-documented behaviour. MS documentation doesn't say if an - // exception should occur or not and it has slightly inconsistent behaviour if we sent 3 bytes (of 4), or 4 + // The behavior isn't the same due to non-documented behavior. MS documentation doesn't say if an + // exception should occur or not and it has slightly inconsistent behavior if we sent 3 bytes (of 4), or 4 // bytes at once. - int bu; + Decoder d = Encoding.UTF8.GetDecoder(); int cu; + int bu; bool complete; bool exception = false; + char[] c = new char[30]; + CircularBuffer cb = new CircularBuffer(m, 28, 4); try { + // An exception might be raised to say that the data can't be converted to fit in the output memory + // buffer. But then nothing should be consumed. Instead, it may be bytes are consumed and no exception, + // or no bytes are consumed and an exception. d.Convert(cb, c, 0, 1, false, out bu, out cu, out complete); + Assert.That(bu, Is.Not.EqualTo(0)); // If no exception, then bytes must be consumed. + Assert.That(cb.Length, Is.EqualTo(4 - bu)); // And that the buffer is reduced by the correct amount. + Assert.That(complete, Is.False); } catch (ArgumentException e) { - if (!e.ParamName.Equals("chars")) throw; + if (e.ParamName == null || !e.ParamName.Equals("chars")) throw; exception = true; cu = -1; } + if (!exception) { + // The conversion must be a single Unicode character, which is two UTF-16. As the previous test allowed + // only 1 UTF16 char, it must be zero if no exception was raised. Assert.That(cu, Is.EqualTo(0)); Assert.That(() => { - d.Convert(cb, c, 0, 1, false, out bu, out cu, out complete); + d.Convert(cb, c, 0, 1, false, out _, out _, out _); }, Throws.TypeOf().With.Property("ParamName").EqualTo("chars")); } + // Second test shows that after a decoder reset, the results are consistent. + + exception = false; cb = new CircularBuffer(m, 28, 4); d.Reset(); - exception = false; try { d.Convert(cb, c, 0, 1, true, out bu, out cu, out complete); + Assert.That(bu, Is.Not.EqualTo(0)); // If no exception, then bytes must be consumed. + Assert.That(cb.Length, Is.EqualTo(4 - bu)); // And that the buffer is reduced by the correct amount. + Assert.That(complete, Is.False); } catch (ArgumentException e) { - if (!e.ParamName.Equals("chars")) throw; + if (e.ParamName == null || !e.ParamName.Equals("chars")) throw; exception = true; cu = -1; } + if (!exception) { Assert.That(cu, Is.EqualTo(0)); Assert.That(() => { - d.Convert(cb, c, 0, 1, false, out bu, out cu, out complete); + d.Convert(cb, c, 0, 1, false, out _, out _, out _); }, Throws.TypeOf().With.Property("ParamName").EqualTo("chars")); } - d.Convert(cb, c, 0, 2, true, out bu, out cu, out complete); + d.Convert(cb, c, 0, 2, true, out _, out cu, out complete); Assert.That(complete, Is.True); Assert.That(cu, Is.EqualTo(2)); + Assert.That(cb.Length, Is.EqualTo(0)); // Show that all data was now consumed } [Test] @@ -1027,15 +1174,16 @@ public void CircularBufferExt_DecoderConvert2_Utf16Chars2() // Based on the test "Decoder_Utf16Chars2" Decoder d = Encoding.UTF8.GetDecoder(); - d.Convert(cb, cc, 13, false, out _, out int cu, out bool complete); + d.Convert(cb, cc, 13, false, out int bu, out int cu, out bool complete); Assert.That(complete, Is.False); Assert.That(cu, Is.EqualTo(12)); Assert.That(cc.GetString(), Is.EqualTo("OPQRSTUVWXYZ")); + Assert.That(cb.Length, Is.EqualTo(16 - bu)); // This particular test is hard. The decoder consumes 12 bytes, but our function consumes more (because the // 4-bytes cross a boundary). The decoder needs to see all four bytes to decide not to convert it. There is // nothing in the documentation to say that the decoder should behave this way. So we can't simulate the - // original behaviour in this case. + // original behavior in this case. } [Test] @@ -1047,14 +1195,11 @@ public void CircularBufferExt_DecoderConvert2_Utf16Chars3() 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0xF3, 0xA0 }; - char[] c = new char[30]; - CircularBuffer cb = new CircularBuffer(m, 28, 4); - CircularBuffer cc = new CircularBuffer(c, 5, 0); // Based on the test "Decoder_Utf16Chars3" - // The behaviour isn't the same due to non-documented behaviour. MS documentation doesn't say if an - // exception should occur or not and it has slightly inconsistent behaviour if we sent 3 bytes (of 4), or 4 + // The behavior isn't the same due to non-documented behavior. MS documentation doesn't say if an + // exception should occur or not and it has slightly inconsistent behavior if we sent 3 bytes (of 4), or 4 // bytes at once. int bu; @@ -1062,40 +1207,57 @@ public void CircularBufferExt_DecoderConvert2_Utf16Chars3() bool complete; bool exception = false; Decoder d = Encoding.UTF8.GetDecoder(); + char[] c = new char[30]; + CircularBuffer cb = new CircularBuffer(m, 28, 4); + CircularBuffer cc = new CircularBuffer(c, 5, 0); try { + // An exception might be raised to say that the data can't be converted to fit in the output memory + // buffer. But then nothing should be consumed. Instead, it may be bytes are consumed and no exception, + // or no bytes are consumed and an exception. d.Convert(cb, cc, 1, false, out bu, out cu, out complete); + Assert.That(bu, Is.Not.EqualTo(0)); // If no exception, then bytes must be consumed. + Assert.That(cb.Length, Is.EqualTo(4 - bu)); // And that the buffer is reduced by the correct amount. + Assert.That(complete, Is.False); } catch (ArgumentException e) { - if (!e.ParamName.Equals("chars")) throw; + if (e.ParamName == null || !e.ParamName.Equals("chars")) throw; exception = true; cu = -1; } if (!exception) { + // The conversion must be a single Unicode character, which is two UTF-16. As the previous test allowed + // only 1 UTF16 char, it must be zero if no exception was raised. Assert.That(cu, Is.EqualTo(0)); Assert.That(() => { - d.Convert(cb, cc, 1, false, out bu, out cu, out complete); + d.Convert(cb, cc, 1, false, out _, out _, out _); }, Throws.TypeOf().With.Property("ParamName").EqualTo("chars")); } + // Second test shows that after a decoder reset, the results are consistent. + + exception = false; cb = new CircularBuffer(m, 28, 4); d.Reset(); - exception = false; try { d.Convert(cb, cc, 1, true, out bu, out cu, out complete); + Assert.That(bu, Is.Not.EqualTo(0)); // If no exception, then bytes must be consumed. + Assert.That(cb.Length, Is.EqualTo(4 - bu)); // And that the buffer is reduced by the correct amount. + Assert.That(complete, Is.False); } catch (ArgumentException e) { - if (!e.ParamName.Equals("chars")) throw; + if (e.ParamName == null || !e.ParamName.Equals("chars")) throw; exception = true; cu = -1; } if (!exception) { Assert.That(cu, Is.EqualTo(0)); Assert.That(() => { - d.Convert(cb, cc, 1, false, out bu, out cu, out complete); + d.Convert(cb, cc, 1, false, out _, out _, out _); }, Throws.TypeOf().With.Property("ParamName").EqualTo("chars")); } - d.Convert(cb, cc, 2, true, out bu, out cu, out complete); + d.Convert(cb, cc, 2, true, out _, out cu, out complete); Assert.That(complete, Is.True); Assert.That(cu, Is.EqualTo(2)); + Assert.That(cb.Length, Is.EqualTo(0)); // Show that all data was now consumed } [Test] @@ -1162,12 +1324,11 @@ public void CircularBufferExt_DecoderConvert2_MultiChar2() Assert.That(cc.GetString(), Is.EqualTo("0123456789@ABCDIJKLMNOPQRSTU")); // There are no bytes to convert, an exception should be raised like the real decoder in a char[]. - try { + Assert.That(() => { d.Convert(cb, cc, cc.Free, false, out bu, out cu, out complete); - } catch (System.ArgumentException e) { - if (!e.ParamName.Equals("chars")) throw; - } + }, Throws.TypeOf().With.Property("ParamName").EqualTo("chars")); } + [Test] public void CircularBufferExt_DecoderConvert3_BoundariesWithFlush() { @@ -1492,21 +1653,18 @@ public void CircularBufferExt_DecoderConvert4_Utf16Chars3() // We expect this to fail, as a two-char Unicode character doesn't fit in one byte Encoding enc = Encoding.GetEncoding("UTF-8", new EncoderReplacementFallback("."), new DecoderReplacementFallback(".")); Decoder d = enc.GetDecoder(); - int bu; - int cu; - bool complete; Assert.That(() => { - d.Convert(m, 12, 10, cc, false, out bu, out cu, out complete); + d.Convert(m, 12, 10, cc, false, out _, out _, out _); }, Throws.TypeOf().With.Property("ParamName").EqualTo("chars")); // We expect this to fail, as a two-char Unicode character doesn't fit in one byte Assert.That(() => { - d.Convert(m, 12, 10, cc, true, out bu, out cu, out complete); + d.Convert(m, 12, 10, cc, false, out _, out _, out _); }, Throws.TypeOf().With.Property("ParamName").EqualTo("chars")); c = new char[2]; cc = new CircularBuffer(c, 1, 0); - d.Convert(m, 12, 4, cc, true, out bu, out cu, out complete); + d.Convert(m, 12, 4, cc, true, out int bu, out int cu, out bool complete); Assert.That(complete, Is.True); Assert.That(bu, Is.EqualTo(4)); Assert.That(cu, Is.EqualTo(2));