ByteArray.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <?php
  2. namespace Aws\Crypto\Polyfill;
  3. /**
  4. * Class ByteArray
  5. * @package Aws\Crypto\Polyfill
  6. */
  7. class ByteArray extends \SplFixedArray
  8. {
  9. use NeedsTrait;
  10. /**
  11. * ByteArray constructor.
  12. *
  13. * @param int|string|int[] $size
  14. * If you pass in an integer, it creates a ByteArray of that size.
  15. * If you pass in a string or array, it converts it to an array of
  16. * integers between 0 and 255.
  17. * @throws \InvalidArgumentException
  18. */
  19. public function __construct($size = 0)
  20. {
  21. $arr = null;
  22. // Integer? This behaves just like SplFixedArray.
  23. if (\is_array($size)) {
  24. // Array? We need to pass the count to parent::__construct() then populate
  25. $arr = $size;
  26. $size = \count($arr);
  27. } elseif (\is_string($size)) {
  28. // We need to avoid mbstring.func_overload
  29. if (\is_callable('\\mb_str_split')) {
  30. $tmp = \mb_str_split($size, 1, '8bit');
  31. } else {
  32. $tmp = \str_split($size, 1);
  33. }
  34. // Let's convert each character to an 8-bit integer and store in $arr
  35. $arr = [];
  36. if (!empty($tmp)) {
  37. foreach ($tmp as $t) {
  38. if (strlen($t) < 1) {
  39. continue;
  40. }
  41. $arr []= \unpack('C', $t)[1] & 0xff;
  42. }
  43. }
  44. $size = \count($arr);
  45. } elseif ($size instanceof ByteArray) {
  46. $arr = $size->toArray();
  47. $size = $size->count();
  48. } elseif (!\is_int($size)) {
  49. throw new \InvalidArgumentException(
  50. 'Argument must be an integer, string, or array of integers.'
  51. );
  52. }
  53. parent::__construct($size);
  54. if (!empty($arr)) {
  55. // Populate this object with values from constructor argument
  56. foreach ($arr as $i => $v) {
  57. $this->offsetSet($i, $v);
  58. }
  59. } else {
  60. // Initialize to zero.
  61. for ($i = 0; $i < $size; ++$i) {
  62. $this->offsetSet($i, 0);
  63. }
  64. }
  65. }
  66. /**
  67. * Encode an integer into a byte array. 32-bit (unsigned), big endian byte order.
  68. *
  69. * @param int $num
  70. * @return self
  71. */
  72. public static function enc32be($num)
  73. {
  74. return new ByteArray(\pack('N', $num));
  75. }
  76. /**
  77. * @param ByteArray $other
  78. * @return bool
  79. */
  80. public function equals(ByteArray $other)
  81. {
  82. if ($this->count() !== $other->count()) {
  83. return false;
  84. }
  85. $d = 0;
  86. for ($i = $this->count() - 1; $i >= 0; --$i) {
  87. $d |= $this[$i] ^ $other[$i];
  88. }
  89. return $d === 0;
  90. }
  91. /**
  92. * @param ByteArray $array
  93. * @return ByteArray
  94. */
  95. public function exclusiveOr(ByteArray $array)
  96. {
  97. self::needs(
  98. $this->count() === $array->count(),
  99. 'Both ByteArrays must be equal size for exclusiveOr()'
  100. );
  101. $out = clone $this;
  102. for ($i = 0; $i < $this->count(); ++$i) {
  103. $out[$i] = $array[$i] ^ $out[$i];
  104. }
  105. return $out;
  106. }
  107. /**
  108. * Returns a new ByteArray incremented by 1 (big endian byte order).
  109. *
  110. * @param int $increase
  111. * @return self
  112. */
  113. public function getIncremented($increase = 1)
  114. {
  115. $clone = clone $this;
  116. $index = $clone->count();
  117. while ($index > 0) {
  118. --$index;
  119. $tmp = ($clone[$index] + $increase) & PHP_INT_MAX;
  120. $clone[$index] = $tmp & 0xff;
  121. $increase = $tmp >> 8;
  122. }
  123. return $clone;
  124. }
  125. /**
  126. * Sets a value. See SplFixedArray for more.
  127. *
  128. * @param int $index
  129. * @param int $newval
  130. * @return void
  131. */
  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. }