ByteArray.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <?php
  2. namespace Aws\Crypto\Polyfill;
  3. /**
  4. * Class ByteArray
  5. */
  6. class ByteArray extends \SplFixedArray
  7. {
  8. use NeedsTrait;
  9. /**
  10. * ByteArray constructor.
  11. *
  12. * @param int|string|int[] $size
  13. * If you pass in an integer, it creates a ByteArray of that size.
  14. * If you pass in a string or array, it converts it to an array of
  15. * integers between 0 and 255.
  16. * @throws \InvalidArgumentException
  17. */
  18. public function __construct($size = 0)
  19. {
  20. $arr = null;
  21. // Integer? This behaves just like SplFixedArray.
  22. if (\is_array($size)) {
  23. // Array? We need to pass the count to parent::__construct() then populate
  24. $arr = $size;
  25. $size = \count($arr);
  26. } elseif (\is_string($size)) {
  27. // We need to avoid mbstring.func_overload
  28. if (\is_callable('\\mb_str_split')) {
  29. $tmp = \mb_str_split($size, 1, '8bit');
  30. } else {
  31. $tmp = \str_split($size, 1);
  32. }
  33. // Let's convert each character to an 8-bit integer and store in $arr
  34. $arr = [];
  35. if (!empty($tmp)) {
  36. foreach ($tmp as $t) {
  37. if (strlen($t) < 1) {
  38. continue;
  39. }
  40. $arr []= \unpack('C', $t)[1] & 0xff;
  41. }
  42. }
  43. $size = \count($arr);
  44. } elseif ($size instanceof ByteArray) {
  45. $arr = $size->toArray();
  46. $size = $size->count();
  47. } elseif (!\is_int($size)) {
  48. throw new \InvalidArgumentException(
  49. 'Argument must be an integer, string, or array of integers.'
  50. );
  51. }
  52. parent::__construct($size);
  53. if (!empty($arr)) {
  54. // Populate this object with values from constructor argument
  55. foreach ($arr as $i => $v) {
  56. $this->offsetSet($i, $v);
  57. }
  58. } else {
  59. // Initialize to zero.
  60. for ($i = 0; $i < $size; ++$i) {
  61. $this->offsetSet($i, 0);
  62. }
  63. }
  64. }
  65. /**
  66. * Encode an integer into a byte array. 32-bit (unsigned), big endian byte order.
  67. *
  68. * @param int $num
  69. * @return self
  70. */
  71. public static function enc32be($num)
  72. {
  73. return new ByteArray(\pack('N', $num));
  74. }
  75. /**
  76. * @param ByteArray $other
  77. * @return bool
  78. */
  79. public function equals(ByteArray $other)
  80. {
  81. if ($this->count() !== $other->count()) {
  82. return false;
  83. }
  84. $d = 0;
  85. for ($i = $this->count() - 1; $i >= 0; --$i) {
  86. $d |= $this[$i] ^ $other[$i];
  87. }
  88. return $d === 0;
  89. }
  90. /**
  91. * @param ByteArray $array
  92. * @return ByteArray
  93. */
  94. public function exclusiveOr(ByteArray $array)
  95. {
  96. self::needs(
  97. $this->count() === $array->count(),
  98. 'Both ByteArrays must be equal size for exclusiveOr()'
  99. );
  100. $out = clone $this;
  101. for ($i = 0; $i < $this->count(); ++$i) {
  102. $out[$i] = $array[$i] ^ $out[$i];
  103. }
  104. return $out;
  105. }
  106. /**
  107. * Returns a new ByteArray incremented by 1 (big endian byte order).
  108. *
  109. * @param int $increase
  110. * @return self
  111. */
  112. public function getIncremented($increase = 1)
  113. {
  114. $clone = clone $this;
  115. $index = $clone->count();
  116. while ($index > 0) {
  117. --$index;
  118. $tmp = ($clone[$index] + $increase) & PHP_INT_MAX;
  119. $clone[$index] = $tmp & 0xff;
  120. $increase = $tmp >> 8;
  121. }
  122. return $clone;
  123. }
  124. /**
  125. * Sets a value. See SplFixedArray for more.
  126. *
  127. * @param int $index
  128. * @param int $newval
  129. * @return void
  130. */
  131. #[\ReturnTypeWillChange]
  132. public function offsetSet($index, $newval)
  133. {
  134. parent::offsetSet($index, $newval & 0xff);
  135. }
  136. /**
  137. * Return a copy of this ByteArray, bitshifted to the right by 1.
  138. * Used in Gmac.
  139. *
  140. * @return self
  141. */
  142. public function rshift()
  143. {
  144. $out = clone $this;
  145. for ($j = $this->count() - 1; $j > 0; --$j) {
  146. $out[$j] = (($out[$j - 1] & 1) << 7) | ($out[$j] >> 1);
  147. }
  148. $out[0] >>= 1;
  149. return $out;
  150. }
  151. /**
  152. * Constant-time conditional select. This is meant to read like a ternary operator.
  153. *
  154. * $z = ByteArray::select(1, $x, $y); // $z is equal to $x
  155. * $z = ByteArray::select(0, $x, $y); // $z is equal to $y
  156. *
  157. * @param int $select
  158. * @param ByteArray $left
  159. * @param ByteArray $right
  160. * @return ByteArray
  161. */
  162. public static function select($select, ByteArray $left, ByteArray $right)
  163. {
  164. self::needs(
  165. $left->count() === $right->count(),
  166. 'Both ByteArrays must be equal size for select()'
  167. );
  168. $rightLength = $right->count();
  169. $out = clone $right;
  170. $mask = (-($select & 1)) & 0xff;
  171. for ($i = 0; $i < $rightLength; $i++) {
  172. $out[$i] = $out[$i] ^ (($left[$i] ^ $right[$i]) & $mask);
  173. }
  174. return $out;
  175. }
  176. /**
  177. * Overwrite values of this ByteArray based on a separate ByteArray, with
  178. * a given starting offset and length.
  179. *
  180. * See JavaScript's Uint8Array.set() for more information.
  181. *
  182. * @param ByteArray $input
  183. * @param int $offset
  184. * @param int|null $length
  185. * @return self
  186. */
  187. public function set(ByteArray $input, $offset = 0, $length = null)
  188. {
  189. self::needs(
  190. is_int($offset) && $offset >= 0,
  191. 'Offset must be a positive integer or zero'
  192. );
  193. if (is_null($length)) {
  194. $length = $input->count();
  195. }
  196. $i = 0; $j = $offset;
  197. while ($i < $length && $j < $this->count()) {
  198. $this[$j] = $input[$i];
  199. ++$i;
  200. ++$j;
  201. }
  202. return $this;
  203. }
  204. /**
  205. * Returns a slice of this ByteArray.
  206. *
  207. * @param int $start
  208. * @param null $length
  209. * @return self
  210. */
  211. public function slice($start = 0, $length = null)
  212. {
  213. return new ByteArray(\array_slice($this->toArray(), $start, $length));
  214. }
  215. /**
  216. * Mutates the current state and sets all values to zero.
  217. *
  218. * @return void
  219. */
  220. public function zeroize()
  221. {
  222. for ($i = $this->count() - 1; $i >= 0; --$i) {
  223. $this->offsetSet($i, 0);
  224. }
  225. }
  226. /**
  227. * Converts the ByteArray to a raw binary string.
  228. *
  229. * @return string
  230. */
  231. public function toString()
  232. {
  233. $count = $this->count();
  234. if ($count === 0) {
  235. return '';
  236. }
  237. $args = $this->toArray();
  238. \array_unshift($args, \str_repeat('C', $count));
  239. // constant-time, PHP <5.6 equivalent to pack('C*', ...$args);
  240. return \call_user_func_array('\\pack', $args);
  241. }
  242. }