You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

ByteCircularBuffer.cs 23 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. #region Original License
  2. //New BSD License(BSD)
  3. //
  4. //Copyright(c) 2014-2015 Cyotek Ltd
  5. //Copyright(c) 2012, Alex Regueiro
  6. //All rights reserved.
  7. //
  8. //Redistribution and use in source and binary forms, with or without
  9. //modification, are permitted provided that the following conditions are met:
  10. // * Redistributions of source code must retain the above copyright
  11. // notice, this list of conditions and the following disclaimer.
  12. // * Redistributions in binary form must reproduce the above copyright
  13. // notice, this list of conditions and the following disclaimer in the
  14. // documentation and/or other materials provided with the distribution.
  15. // * Neither the name of Cyotek nor the
  16. // names of its contributors may be used to endorse or promote products
  17. // derived from this software without specific prior written permission.
  18. //
  19. //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  20. //ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  21. //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  22. //DISCLAIMED.IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  23. //DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  24. //(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  25. //LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  26. //ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  28. //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. #endregion
  30. using System;
  31. using System.Collections.Generic;
  32. namespace Shadowsocks.Encryption.CircularBuffer
  33. {
  34. /// <summary>
  35. /// Represents a first-in, first-out collection of objects using a fixed buffer.
  36. /// </summary>
  37. /// <remarks>
  38. /// <para>The capacity of a <see cref="ByteCircularBuffer" /> is the number of elements the <see cref="ByteCircularBuffer"/> can hold. </para>
  39. /// <para>ByteCircularBuffer accepts <c>null</c> as a valid value for reference types and allows duplicate elements.</para>
  40. /// <para>The <see cref="Get()"/> methods will remove the items that are returned from the ByteCircularBuffer. To view the contents of the ByteCircularBuffer without removing items, use the <see cref="Peek()"/> or <see cref="PeekLast"/> methods.</para>
  41. /// </remarks>
  42. public class ByteCircularBuffer
  43. {
  44. // based on http://circularbuffer.codeplex.com/
  45. // http://en.wikipedia.org/wiki/Circular_buffer
  46. // modified from https://github.com/cyotek/Cyotek.Collections.Generic.CircularBuffer
  47. // some code taken from https://github.com/xorxornop/RingBuffer
  48. // and https://github.com/xorxornop/PerfCopy
  49. #region Instance Fields
  50. private byte[] _buffer;
  51. private int _capacity;
  52. #endregion
  53. #region Public Constructors
  54. /// <summary>
  55. /// Initializes a new instance of the <see cref="ByteCircularBuffer"/> class that is empty and has the specified initial capacity.
  56. /// </summary>
  57. /// <param name="capacity">The maximum capcity of the buffer.</param>
  58. /// <exception cref="System.ArgumentException">Thown if the <paramref name="capacity"/> is less than zero.</exception>
  59. public ByteCircularBuffer(int capacity)
  60. {
  61. if (capacity < 0)
  62. {
  63. throw new ArgumentException("The buffer capacity must be greater than or equal to zero.",
  64. nameof(capacity));
  65. }
  66. _buffer = new byte[capacity];
  67. this.Capacity = capacity;
  68. this.Size = 0;
  69. this.Head = 0;
  70. this.Tail = 0;
  71. }
  72. #endregion
  73. #region Public Properties
  74. /// <summary>
  75. /// Gets or sets the total number of elements the internal data structure can hold.
  76. /// </summary>
  77. /// <value>The total number of elements that the <see cref="ByteCircularBuffer"/> can contain.</value>
  78. /// <exception cref="System.ArgumentOutOfRangeException">Thrown if the specified new capacity is smaller than the current contents of the buffer.</exception>
  79. public int Capacity
  80. {
  81. get { return _capacity; }
  82. set
  83. {
  84. if (value != _capacity)
  85. {
  86. if (value < this.Size)
  87. {
  88. throw new ArgumentOutOfRangeException(nameof(value), value,
  89. "The new capacity must be greater than or equal to the buffer size.");
  90. }
  91. var newBuffer = new byte[value];
  92. if (this.Size > 0)
  93. {
  94. this.CopyTo(newBuffer);
  95. }
  96. _buffer = newBuffer;
  97. _capacity = value;
  98. }
  99. }
  100. }
  101. /// <summary>
  102. /// Gets the index of the beginning of the buffer data.
  103. /// </summary>
  104. /// <value>The index of the first element in the buffer.</value>
  105. public int Head { get; protected set; }
  106. /// <summary>
  107. /// Gets a value indicating whether the buffer is empty.
  108. /// </summary>
  109. /// <value><c>true</c> if buffer is empty; otherwise, <c>false</c>.</value>
  110. public virtual bool IsEmpty => this.Size == 0;
  111. /// <summary>
  112. /// Gets a value indicating whether the buffer is full.
  113. /// </summary>
  114. /// <value><c>true</c> if the buffer is full; otherwise, <c>false</c>.</value>
  115. /// <remarks>The <see cref="IsFull"/> property always returns <c>false</c> if the <see cref="AllowOverwrite"/> property is set to <c>true</c>.</remarks>
  116. public virtual bool IsFull => this.Size == this.Capacity;
  117. /// <summary>
  118. /// Gets the number of elements contained in the <see cref="ByteCircularBuffer"/>.
  119. /// </summary>
  120. /// <value>The number of elements contained in the <see cref="ByteCircularBuffer"/>.</value>
  121. public int Size { get; protected set; }
  122. /// <summary>
  123. /// Gets the index of the end of the buffer data.
  124. /// </summary>
  125. /// <value>The index of the last element in the buffer.</value>
  126. public int Tail { get; protected set; }
  127. #endregion
  128. #region Public Members
  129. /// <summary>
  130. /// Removes all items from the <see cref="ByteCircularBuffer" />.
  131. /// </summary>
  132. public void Clear()
  133. {
  134. this.Size = 0;
  135. this.Head = 0;
  136. this.Tail = 0;
  137. _buffer = new byte[this.Capacity];
  138. }
  139. /// <summary>
  140. /// Determines whether the <see cref="ByteCircularBuffer" /> contains a specific value.
  141. /// </summary>
  142. /// <param name="item">The object to locate in the <see cref="ByteCircularBuffer" />.</param>
  143. /// <returns><c>true</c> if <paramref name="item" /> is found in the <see cref="ByteCircularBuffer" />; otherwise, <c>false</c>.</returns>
  144. public bool Contains(byte item)
  145. {
  146. var bufferIndex = this.Head;
  147. var comparer = EqualityComparer<byte>.Default;
  148. var result = false;
  149. for (int i = 0; i < this.Size; i++, bufferIndex++)
  150. {
  151. if (bufferIndex == this.Capacity)
  152. {
  153. bufferIndex = 0;
  154. }
  155. if (comparer.Equals(_buffer[bufferIndex], item))
  156. {
  157. result = true;
  158. break;
  159. }
  160. }
  161. return result;
  162. }
  163. /// <summary>
  164. /// Copies the entire <see cref="ByteCircularBuffer"/> to a compatible one-dimensional array, starting at the beginning of the target array.
  165. /// </summary>
  166. /// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
  167. public void CopyTo(byte[] array)
  168. {
  169. this.CopyTo(array, 0);
  170. }
  171. /// <summary>
  172. /// Copies the entire <see cref="ByteCircularBuffer"/> to a compatible one-dimensional array, starting at the specified index of the target array.
  173. /// </summary>
  174. /// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
  175. /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
  176. public void CopyTo(byte[] array, int arrayIndex)
  177. {
  178. this.CopyTo(this.Head, array, arrayIndex, Math.Min(this.Size, array.Length - arrayIndex));
  179. }
  180. /// <summary>
  181. /// Copies a range of elements from the <see cref="ByteCircularBuffer"/> to a compatible one-dimensional array, starting at the specified index of the target array.
  182. /// </summary>
  183. /// <param name="index">The zero-based index in the source <see cref="ByteCircularBuffer"/> at which copying begins.</param>
  184. /// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
  185. /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
  186. /// <param name="count">The number of elements to copy.</param>
  187. public virtual void CopyTo(int index, byte[] array, int arrayIndex, int count)
  188. {
  189. if (count > this.Size)
  190. {
  191. throw new ArgumentOutOfRangeException(nameof(count), count,
  192. "The read count cannot be greater than the buffer size.");
  193. }
  194. var startAnchor = index;
  195. var dstIndex = arrayIndex;
  196. while (count > 0)
  197. {
  198. int chunk = Math.Min(Capacity - startAnchor, count);
  199. Buffer.BlockCopy(_buffer, startAnchor, array, dstIndex, chunk);
  200. startAnchor = (startAnchor + chunk == Capacity) ? 0 : startAnchor + chunk;
  201. dstIndex += chunk;
  202. count -= chunk;
  203. }
  204. }
  205. /// <summary>
  206. /// Removes and returns the specified number of objects from the beginning of the <see cref="ByteCircularBuffer"/>.
  207. /// </summary>
  208. /// <param name="count">The number of elements to remove and return from the <see cref="ByteCircularBuffer"/>.</param>
  209. /// <returns>The objects that are removed from the beginning of the <see cref="ByteCircularBuffer"/>.</returns>
  210. public byte[] Get(int count)
  211. {
  212. if (count <= 0) throw new ArgumentOutOfRangeException("should greater than 0");
  213. var result = new byte[count];
  214. this.Get(result);
  215. return result;
  216. }
  217. /// <summary>
  218. /// Copies and removes the specified number elements from the <see cref="ByteCircularBuffer"/> to a compatible one-dimensional array, starting at the beginning of the target array.
  219. /// </summary>
  220. /// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
  221. /// <returns>The actual number of elements copied into <paramref name="array"/>.</returns>
  222. public int Get(byte[] array)
  223. {
  224. if (array.Length <= 0) throw new ArgumentOutOfRangeException("should greater than 0");
  225. return this.Get(array, 0, array.Length);
  226. }
  227. /// <summary>
  228. /// Copies and removes the specified number elements from the <see cref="ByteCircularBuffer"/> to a compatible one-dimensional array, starting at the specified index of the target array.
  229. /// </summary>
  230. /// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
  231. /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
  232. /// <param name="count">The number of elements to copy.</param>
  233. /// <returns>The actual number of elements copied into <paramref name="array"/>.</returns>
  234. public virtual int Get(byte[] array, int arrayIndex, int count)
  235. {
  236. if (arrayIndex < 0)
  237. {
  238. throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Negative offset specified. Offsets must be positive.");
  239. }
  240. if (count < 0)
  241. {
  242. throw new ArgumentOutOfRangeException(nameof(count), "Negative count specified. Count must be positive.");
  243. }
  244. if (count > this.Size)
  245. {
  246. throw new ArgumentException("Ringbuffer contents insufficient for take/read operation.", nameof(count));
  247. }
  248. if (array.Length < arrayIndex + count)
  249. {
  250. throw new ArgumentException("Destination array too small for requested output.");
  251. }
  252. var bytesCopied = 0;
  253. var dstIndex = arrayIndex;
  254. while (count > 0)
  255. {
  256. int chunk = Math.Min(Capacity - this.Head, count);
  257. Buffer.BlockCopy(_buffer, this.Head, array, dstIndex, chunk);
  258. this.Head = (this.Head + chunk == Capacity) ? 0 : this.Head + chunk;
  259. this.Size -= chunk;
  260. dstIndex += chunk;
  261. bytesCopied += chunk;
  262. count -= chunk;
  263. }
  264. return bytesCopied;
  265. }
  266. /// <summary>
  267. /// Removes and returns the object at the beginning of the <see cref="ByteCircularBuffer"/>.
  268. /// </summary>
  269. /// <returns>The object that is removed from the beginning of the <see cref="ByteCircularBuffer"/>.</returns>
  270. /// <exception cref="System.InvalidOperationException">Thrown if the buffer is empty.</exception>
  271. /// <remarks>This method is similar to the <see cref="Peek()"/> method, but <c>Peek</c> does not modify the <see cref="ByteCircularBuffer"/>.</remarks>
  272. public virtual byte Get()
  273. {
  274. if (this.IsEmpty)
  275. {
  276. throw new InvalidOperationException("The buffer is empty.");
  277. }
  278. var item = _buffer[this.Head];
  279. if (++this.Head == this.Capacity)
  280. {
  281. this.Head = 0;
  282. }
  283. this.Size--;
  284. return item;
  285. }
  286. /// <summary>
  287. /// Returns the object at the beginning of the <see cref="ByteCircularBuffer"/> without removing it.
  288. /// </summary>
  289. /// <returns>The object at the beginning of the <see cref="ByteCircularBuffer"/>.</returns>
  290. /// <exception cref="System.InvalidOperationException">Thrown if the buffer is empty.</exception>
  291. public virtual byte Peek()
  292. {
  293. if (this.IsEmpty)
  294. {
  295. throw new InvalidOperationException("The buffer is empty.");
  296. }
  297. var item = _buffer[this.Head];
  298. return item;
  299. }
  300. /// <summary>
  301. /// Returns the specified number of objects from the beginning of the <see cref="ByteCircularBuffer"/>.
  302. /// </summary>
  303. /// <param name="count">The number of elements to return from the <see cref="ByteCircularBuffer"/>.</param>
  304. /// <returns>The objects that from the beginning of the <see cref="ByteCircularBuffer"/>.</returns>
  305. /// <exception cref="System.InvalidOperationException">Thrown if the buffer is empty.</exception>
  306. public virtual byte[] Peek(int count)
  307. {
  308. if (this.IsEmpty)
  309. {
  310. throw new InvalidOperationException("The buffer is empty.");
  311. }
  312. var items = new byte[count];
  313. this.CopyTo(items);
  314. return items;
  315. }
  316. /// <summary>
  317. /// Returns the object at the end of the <see cref="ByteCircularBuffer"/> without removing it.
  318. /// </summary>
  319. /// <returns>The object at the end of the <see cref="ByteCircularBuffer"/>.</returns>
  320. /// <exception cref="System.InvalidOperationException">Thrown if the buffer is empty.</exception>
  321. public virtual byte PeekLast()
  322. {
  323. int bufferIndex;
  324. if (this.IsEmpty)
  325. {
  326. throw new InvalidOperationException("The buffer is empty.");
  327. }
  328. if (this.Tail == 0)
  329. {
  330. bufferIndex = this.Size - 1;
  331. }
  332. else
  333. {
  334. bufferIndex = this.Tail - 1;
  335. }
  336. var item = _buffer[bufferIndex];
  337. return item;
  338. }
  339. /// <summary>
  340. /// Copies an entire compatible one-dimensional array to the <see cref="ByteCircularBuffer"/>.
  341. /// </summary>
  342. /// <param name="array">The one-dimensional <see cref="Array"/> that is the source of the elements copied to <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
  343. /// <exception cref="System.InvalidOperationException">Thrown if buffer does not have sufficient capacity to put in new items.</exception>
  344. /// <remarks>If <see cref="Size"/> plus the size of <paramref name="array"/> exceeds the capacity of the <see cref="ByteCircularBuffer"/> and the <see cref="AllowOverwrite"/> property is <c>true</c>, the oldest items in the <see cref="ByteCircularBuffer"/> are overwritten with <paramref name="array"/>.</remarks>
  345. public int Put(byte[] array)
  346. {
  347. return this.Put(array, 0, array.Length);
  348. }
  349. /// <summary>
  350. /// Copies a range of elements from a compatible one-dimensional array to the <see cref="ByteCircularBuffer"/>.
  351. /// </summary>
  352. /// <param name="array">The one-dimensional <see cref="Array"/> that is the source of the elements copied to <see cref="ByteCircularBuffer"/>. The <see cref="Array"/> must have zero-based indexing.</param>
  353. /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
  354. /// <param name="count">The number of elements to copy.</param>
  355. /// <exception cref="System.InvalidOperationException">Thrown if buffer does not have sufficient capacity to put in new items.</exception>
  356. /// <remarks>If <see cref="Size"/> plus <paramref name="count"/> exceeds the capacity of the <see cref="ByteCircularBuffer"/> and the <see cref="AllowOverwrite"/> property is <c>true</c>, the oldest items in the <see cref="ByteCircularBuffer"/> are overwritten with <paramref name="array"/>.</remarks>
  357. public virtual int Put(byte[] array, int arrayIndex, int count)
  358. {
  359. if (count <= 0) throw new ArgumentOutOfRangeException(nameof(count), "Count must be positive.");
  360. if (this.Size + count > this.Capacity)
  361. {
  362. throw new InvalidOperationException("The buffer does not have sufficient capacity to put new items.");
  363. }
  364. if (array.Length < arrayIndex + count)
  365. {
  366. throw new ArgumentException("Source array too small for requested input.");
  367. }
  368. var srcIndex = arrayIndex;
  369. var bytesToProcess = count;
  370. while (bytesToProcess > 0)
  371. {
  372. int chunk = Math.Min(Capacity - Tail, bytesToProcess);
  373. Buffer.BlockCopy(array, srcIndex, _buffer, Tail, chunk);
  374. Tail = (Tail + chunk == Capacity) ? 0 : Tail + chunk;
  375. this.Size += chunk;
  376. srcIndex += chunk;
  377. bytesToProcess -= chunk;
  378. }
  379. return count;
  380. }
  381. /// <summary>
  382. /// Adds a byte to the end of the <see cref="ByteCircularBuffer"/>.
  383. /// </summary>
  384. /// <param name="item">The byte to add to the <see cref="ByteCircularBuffer"/>. </param>
  385. /// <exception cref="System.InvalidOperationException">Thrown if buffer does not have sufficient capacity to put in new items.</exception>
  386. public virtual void Put(byte item)
  387. {
  388. if (IsFull)
  389. {
  390. throw new InvalidOperationException("The buffer does not have sufficient capacity to put new items.");
  391. }
  392. _buffer[this.Tail] = item;
  393. this.Tail++;
  394. if (this.Size == this.Capacity)
  395. {
  396. this.Head++;
  397. if (this.Head >= this.Capacity)
  398. {
  399. this.Head -= this.Capacity;
  400. }
  401. }
  402. if (this.Tail == this.Capacity)
  403. {
  404. this.Tail = 0;
  405. }
  406. if (this.Size != this.Capacity)
  407. {
  408. this.Size++;
  409. }
  410. }
  411. /// <summary>
  412. /// Increments the starting index of the data buffer in the <see cref="ByteCircularBuffer"/>.
  413. /// </summary>
  414. /// <param name="count">The number of elements to increment the data buffer start index by.</param>
  415. public void Skip(int count)
  416. {
  417. if (count < 0)
  418. {
  419. throw new ArgumentOutOfRangeException(nameof(count), "Negative count specified. Count must be positive.");
  420. }
  421. if (count > this.Size)
  422. {
  423. throw new ArgumentException("Ringbuffer contents insufficient for operation.", nameof(count));
  424. }
  425. // Modular division gives new offset position
  426. this.Head = (this.Head + count) % Capacity;
  427. this.Size -= count;
  428. }
  429. /// <summary>
  430. /// Copies the <see cref="ByteCircularBuffer"/> elements to a new array.
  431. /// </summary>
  432. /// <returns>A new array containing elements copied from the <see cref="ByteCircularBuffer"/>.</returns>
  433. /// <remarks>The <see cref="ByteCircularBuffer"/> is not modified. The order of the elements in the new array is the same as the order of the elements from the beginning of the <see cref="ByteCircularBuffer"/> to its end.</remarks>
  434. public byte[] ToArray()
  435. {
  436. var result = new byte[this.Size];
  437. this.CopyTo(result);
  438. return result;
  439. }
  440. #endregion
  441. }
  442. }