StreamManipulator.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. using System;
  2. namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
  3. {
  4. /// <summary>
  5. /// This class allows us to retrieve a specified number of bits from
  6. /// the input buffer, as well as copy big byte blocks.
  7. ///
  8. /// It uses an int buffer to store up to 31 bits for direct
  9. /// manipulation. This guarantees that we can get at least 16 bits,
  10. /// but we only need at most 15, so this is all safe.
  11. ///
  12. /// There are some optimizations in this class, for example, you must
  13. /// never peek more than 8 bits more than needed, and you must first
  14. /// peek bits before you may drop them. This is not a general purpose
  15. /// class but optimized for the behaviour of the Inflater.
  16. ///
  17. /// authors of the original java version : John Leuner, Jochen Hoenicke
  18. /// </summary>
  19. public class StreamManipulator
  20. {
  21. /// <summary>
  22. /// Get the next sequence of bits but don't increase input pointer. bitCount must be
  23. /// less or equal 16 and if this call succeeds, you must drop
  24. /// at least n - 8 bits in the next call.
  25. /// </summary>
  26. /// <param name="bitCount">The number of bits to peek.</param>
  27. /// <returns>
  28. /// the value of the bits, or -1 if not enough bits available. */
  29. /// </returns>
  30. public int PeekBits(int bitCount)
  31. {
  32. if (bitsInBuffer_ < bitCount)
  33. {
  34. if (windowStart_ == windowEnd_)
  35. {
  36. return -1; // ok
  37. }
  38. buffer_ |= (uint)((window_[windowStart_++] & 0xff |
  39. (window_[windowStart_++] & 0xff) << 8) << bitsInBuffer_);
  40. bitsInBuffer_ += 16;
  41. }
  42. return (int)(buffer_ & ((1 << bitCount) - 1));
  43. }
  44. /// <summary>
  45. /// Tries to grab the next <paramref name="bitCount"/> bits from the input and
  46. /// sets <paramref name="output"/> to the value, adding <paramref name="outputOffset"/>.
  47. /// </summary>
  48. /// <returns>true if enough bits could be read, otherwise false</returns>
  49. public bool TryGetBits(int bitCount, ref int output, int outputOffset = 0)
  50. {
  51. var bits = PeekBits(bitCount);
  52. if (bits < 0)
  53. {
  54. return false;
  55. }
  56. output = bits + outputOffset;
  57. DropBits(bitCount);
  58. return true;
  59. }
  60. /// <summary>
  61. /// Tries to grab the next <paramref name="bitCount"/> bits from the input and
  62. /// sets <paramref name="index"/> of <paramref name="array"/> to the value.
  63. /// </summary>
  64. /// <returns>true if enough bits could be read, otherwise false</returns>
  65. public bool TryGetBits(int bitCount, ref byte[] array, int index)
  66. {
  67. var bits = PeekBits(bitCount);
  68. if (bits < 0)
  69. {
  70. return false;
  71. }
  72. array[index] = (byte)bits;
  73. DropBits(bitCount);
  74. return true;
  75. }
  76. /// <summary>
  77. /// Drops the next n bits from the input. You should have called PeekBits
  78. /// with a bigger or equal n before, to make sure that enough bits are in
  79. /// the bit buffer.
  80. /// </summary>
  81. /// <param name="bitCount">The number of bits to drop.</param>
  82. public void DropBits(int bitCount)
  83. {
  84. buffer_ >>= bitCount;
  85. bitsInBuffer_ -= bitCount;
  86. }
  87. /// <summary>
  88. /// Gets the next n bits and increases input pointer. This is equivalent
  89. /// to <see cref="PeekBits"/> followed by <see cref="DropBits"/>, except for correct error handling.
  90. /// </summary>
  91. /// <param name="bitCount">The number of bits to retrieve.</param>
  92. /// <returns>
  93. /// the value of the bits, or -1 if not enough bits available.
  94. /// </returns>
  95. public int GetBits(int bitCount)
  96. {
  97. int bits = PeekBits(bitCount);
  98. if (bits >= 0)
  99. {
  100. DropBits(bitCount);
  101. }
  102. return bits;
  103. }
  104. /// <summary>
  105. /// Gets the number of bits available in the bit buffer. This must be
  106. /// only called when a previous PeekBits() returned -1.
  107. /// </summary>
  108. /// <returns>
  109. /// the number of bits available.
  110. /// </returns>
  111. public int AvailableBits
  112. {
  113. get
  114. {
  115. return bitsInBuffer_;
  116. }
  117. }
  118. /// <summary>
  119. /// Gets the number of bytes available.
  120. /// </summary>
  121. /// <returns>
  122. /// The number of bytes available.
  123. /// </returns>
  124. public int AvailableBytes
  125. {
  126. get
  127. {
  128. return windowEnd_ - windowStart_ + (bitsInBuffer_ >> 3);
  129. }
  130. }
  131. /// <summary>
  132. /// Skips to the next byte boundary.
  133. /// </summary>
  134. public void SkipToByteBoundary()
  135. {
  136. buffer_ >>= (bitsInBuffer_ & 7);
  137. bitsInBuffer_ &= ~7;
  138. }
  139. /// <summary>
  140. /// Returns true when SetInput can be called
  141. /// </summary>
  142. public bool IsNeedingInput
  143. {
  144. get
  145. {
  146. return windowStart_ == windowEnd_;
  147. }
  148. }
  149. /// <summary>
  150. /// Copies bytes from input buffer to output buffer starting
  151. /// at output[offset]. You have to make sure, that the buffer is
  152. /// byte aligned. If not enough bytes are available, copies fewer
  153. /// bytes.
  154. /// </summary>
  155. /// <param name="output">
  156. /// The buffer to copy bytes to.
  157. /// </param>
  158. /// <param name="offset">
  159. /// The offset in the buffer at which copying starts
  160. /// </param>
  161. /// <param name="length">
  162. /// The length to copy, 0 is allowed.
  163. /// </param>
  164. /// <returns>
  165. /// The number of bytes copied, 0 if no bytes were available.
  166. /// </returns>
  167. /// <exception cref="ArgumentOutOfRangeException">
  168. /// Length is less than zero
  169. /// </exception>
  170. /// <exception cref="InvalidOperationException">
  171. /// Bit buffer isnt byte aligned
  172. /// </exception>
  173. public int CopyBytes(byte[] output, int offset, int length)
  174. {
  175. if (length < 0)
  176. {
  177. throw new ArgumentOutOfRangeException(nameof(length));
  178. }
  179. if ((bitsInBuffer_ & 7) != 0)
  180. {
  181. // bits_in_buffer may only be 0 or a multiple of 8
  182. throw new InvalidOperationException("Bit buffer is not byte aligned!");
  183. }
  184. int count = 0;
  185. while ((bitsInBuffer_ > 0) && (length > 0))
  186. {
  187. output[offset++] = (byte)buffer_;
  188. buffer_ >>= 8;
  189. bitsInBuffer_ -= 8;
  190. length--;
  191. count++;
  192. }
  193. if (length == 0)
  194. {
  195. return count;
  196. }
  197. int avail = windowEnd_ - windowStart_;
  198. if (length > avail)
  199. {
  200. length = avail;
  201. }
  202. System.Array.Copy(window_, windowStart_, output, offset, length);
  203. windowStart_ += length;
  204. if (((windowStart_ - windowEnd_) & 1) != 0)
  205. {
  206. // We always want an even number of bytes in input, see peekBits
  207. buffer_ = (uint)(window_[windowStart_++] & 0xff);
  208. bitsInBuffer_ = 8;
  209. }
  210. return count + length;
  211. }
  212. /// <summary>
  213. /// Resets state and empties internal buffers
  214. /// </summary>
  215. public void Reset()
  216. {
  217. buffer_ = 0;
  218. windowStart_ = windowEnd_ = bitsInBuffer_ = 0;
  219. }
  220. /// <summary>
  221. /// Add more input for consumption.
  222. /// Only call when IsNeedingInput returns true
  223. /// </summary>
  224. /// <param name="buffer">data to be input</param>
  225. /// <param name="offset">offset of first byte of input</param>
  226. /// <param name="count">number of bytes of input to add.</param>
  227. public void SetInput(byte[] buffer, int offset, int count)
  228. {
  229. if (buffer == null)
  230. {
  231. throw new ArgumentNullException(nameof(buffer));
  232. }
  233. if (offset < 0)
  234. {
  235. throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative");
  236. }
  237. if (count < 0)
  238. {
  239. throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative");
  240. }
  241. if (windowStart_ < windowEnd_)
  242. {
  243. throw new InvalidOperationException("Old input was not completely processed");
  244. }
  245. int end = offset + count;
  246. // We want to throw an ArrayIndexOutOfBoundsException early.
  247. // Note the check also handles integer wrap around.
  248. if ((offset > end) || (end > buffer.Length))
  249. {
  250. throw new ArgumentOutOfRangeException(nameof(count));
  251. }
  252. if ((count & 1) != 0)
  253. {
  254. // We always want an even number of bytes in input, see PeekBits
  255. buffer_ |= (uint)((buffer[offset++] & 0xff) << bitsInBuffer_);
  256. bitsInBuffer_ += 8;
  257. }
  258. window_ = buffer;
  259. windowStart_ = offset;
  260. windowEnd_ = end;
  261. }
  262. #region Instance Fields
  263. private byte[] window_;
  264. private int windowStart_;
  265. private int windowEnd_;
  266. private uint buffer_;
  267. private int bitsInBuffer_;
  268. #endregion Instance Fields
  269. }
  270. }