PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Carica/Io/ByteArray.php

https://bitbucket.org/tobmaster/carica-io
PHP | 326 lines | 170 code | 23 blank | 133 comment | 31 complexity | dc5e2d36adde713340ab4dd412c2ade6 MD5 | raw file
  1. <?php
  2. namespace Carica\Io {
  3. /**
  4. * Flexible access to an array of bytes, allow bit handling
  5. */
  6. class ByteArray implements \ArrayAccess, \IteratorAggregate, \Countable {
  7. const BIT_ONE = 1;
  8. const BIT_TWO = 2;
  9. const BIT_THREE = 4;
  10. const BIT_FOUR = 8;
  11. const BIT_FIVE = 16;
  12. const BIT_SIX = 32;
  13. const BIT_SEVEN = 64;
  14. const BIT_EIGHT = 128;
  15. private $_bytes = array();
  16. private $_length = 0;
  17. /**
  18. * Create array of bytes and set length.
  19. *
  20. * @param integer $length
  21. * @throws \OutOfRangeException
  22. */
  23. public function __construct($length = 1) {
  24. $this->setLength($length);
  25. }
  26. /**
  27. * Resize the byte array, additional bytes will be filled with zero.
  28. *
  29. * @param integer $length
  30. * @throws \OutOfRangeException
  31. */
  32. public function setLength($length) {
  33. if ((int)$length < 1) {
  34. throw new \OutOfRangeException('Zero or negative length is not possible');
  35. }
  36. $difference = $length - $this->_length;
  37. if ($difference > 0) {
  38. $this->_bytes = array_merge($this->_bytes, array_fill($this->_length, $difference, 0));
  39. } elseif ($difference < 0) {
  40. $this->_bytes = array_slice($this->_bytes, 0, $difference);
  41. }
  42. $this->_length = (int)$length;
  43. }
  44. /**
  45. * Return teh current byte length
  46. *
  47. * @return integer
  48. */
  49. public function getLength() {
  50. return $this->_length;
  51. }
  52. /**
  53. * Get the byte array as an binary string.
  54. *
  55. * @return string
  56. */
  57. public function __toString() {
  58. return call_user_func_array('pack', array_merge(array("C*"), $this->_bytes));
  59. }
  60. /**
  61. * read the bytes from an binary string
  62. *
  63. * @param string $string
  64. * @param boolean $resize to binary string length
  65. * @throws \OutOfBoundsException
  66. */
  67. public function fromString($string, $resize = FALSE) {
  68. if ($resize && strlen($string) != $this->getLength()) {
  69. $this->setLength(strlen($string));
  70. }
  71. $bytes = array_slice(unpack("C*", "\0".$string), 1);
  72. if (count($bytes) >= $this->_length) {
  73. for ($i = 0; $i < $this->_length; ++$i) {
  74. $this->_bytes[$i] = $bytes[$i];
  75. }
  76. } else {
  77. throw new \OutOfBoundsException(
  78. sprintf(
  79. 'Maximum length is "%d". Got "%d".', $this->_length, count($bytes)
  80. )
  81. );
  82. }
  83. }
  84. /**
  85. * Read an hexadecimal encoded binary string
  86. *
  87. * @param string $string
  88. * @param boolean $resize
  89. * @throws \OutOfBoundsException
  90. */
  91. public function fromHexString($string, $resize = FALSE) {
  92. $string = str_replace(' ', '', $string);
  93. $length = floor(strlen($string) / 2);
  94. if ($resize && $length != $this->getLength()) {
  95. $this->setLength($length);
  96. }
  97. if ($length >= $this->_length) {
  98. for ($i = 0; $i < $this->_length; ++$i) {
  99. $this->_bytes[$i] = hexdec(substr($string, $i * 2, 2));
  100. }
  101. } else {
  102. throw new \OutOfBoundsException(
  103. sprintf(
  104. 'Maximum length is "%d". Got "%d".', $this->_length, $length
  105. )
  106. );
  107. }
  108. }
  109. /**
  110. * Get the byte array as an hexdec string.
  111. *
  112. * @return string
  113. */
  114. public function asHex() {
  115. $result = '';
  116. foreach ($this->_bytes as $byte) {
  117. $result .= str_pad(dechex($byte), 2, '0', STR_PAD_LEFT);
  118. }
  119. return $result;
  120. }
  121. /**
  122. * Get the bytes as bit string seperated by spaces.
  123. *
  124. * @return string
  125. */
  126. public function asBitString() {
  127. $result = '';
  128. foreach ($this->_bytes as $byte) {
  129. $result .= ' '.str_pad(decbin($byte), 8, '0', STR_PAD_LEFT);
  130. }
  131. return substr($result, 1);
  132. }
  133. /**
  134. * Get the bytes as array of bytes, only needed for array functions.
  135. *
  136. * @return array
  137. */
  138. public function asArray() {
  139. return $this->_bytes;
  140. }
  141. /**
  142. * Check if the specified byte or bit offset exists.
  143. *
  144. * If the $offset is an array the first element is the byte index and the second the bin index.
  145. *
  146. * If the $offset is an integer it is the byte offset.
  147. *
  148. * @see ArrayAccess::offsetExists()
  149. * @param integer|array(integer,integer) $offset
  150. * @return integer|boolean
  151. */
  152. public function offsetExists($offset) {
  153. try {
  154. $this->validateOffset($offset);
  155. } catch (\OutOfBoundsException $e) {
  156. return FALSE;
  157. }
  158. return TRUE;
  159. }
  160. /**
  161. * Read if the specified byte or bit offset exists.
  162. *
  163. * If the $offset is an array the first element is the byte index and the second the bin index.
  164. * The result in this case is boolean.
  165. *
  166. * If the $offset is an integer it is the byte offset and the result is the byte values as
  167. * integer.
  168. *
  169. * Examples:
  170. * $byte = $bytes[0]; // read the first byte
  171. * $bit = $bytes[[0, 7]]; // read the highest bit of the first byte
  172. *
  173. * @see ArrayAccess::offsetGet()
  174. * @param integer|array(integer,integer) $offset
  175. * @return integer|boolean
  176. */
  177. public function offsetGet($offset) {
  178. $this->validateOffset($offset);
  179. if (is_array($offset)) {
  180. $bit = ($offset[1] > 0) ? 1 << $offset[1] : 1;
  181. return ($this->_bytes[$offset[0]] & $bit) == $bit;
  182. } else {
  183. return $this->_bytes[$offset];
  184. }
  185. }
  186. /**
  187. * Write if the specified byte or bit offset exists.
  188. *
  189. * If the $offset is an array the first element is the byte index and the second the bin index.
  190. * The value has to be boolean in this case.
  191. *
  192. * If the $offset is an integer it is the byte offset and the value is the byte value as
  193. * integer.
  194. *
  195. * Examples:
  196. * $bytes[0] = 42; // write the first byte
  197. * $bytes[[0, 7]] = TRUE; // set the highest bit of the first byte
  198. *
  199. * @see ArrayAccess::offsetSet()
  200. * @param integer|array(integer,integer) $offset
  201. * @param integer|boolean $value
  202. */
  203. public function offsetSet($offset, $value) {
  204. $this->validateOffset($offset);
  205. if (is_array($offset)) {
  206. $bit = ($offset[1] > 0) ? 1 << $offset[1] : 1;
  207. if ($value) {
  208. $this->_bytes[$offset[0]] = $this->_bytes[$offset[0]] | $bit;
  209. } else {
  210. $this->_bytes[$offset[0]] = $this->_bytes[$offset[0]] & ~$bit;
  211. }
  212. } else {
  213. $this->validateValue($value);
  214. $this->_bytes[$offset] = $value;
  215. }
  216. }
  217. /**
  218. * Reset an byte or bit
  219. *
  220. * If the index spezifies a byte it is set to zero. If it spdifies and bit it is disabled.
  221. *
  222. * @see ArrayAccess::offsetUnset()
  223. */
  224. public function offsetUnset($offset) {
  225. $this->offsetSet($offset, is_array($offset) ? FALSE : 0);
  226. }
  227. /**
  228. * Validate the given offset is usable as byte or bit offset
  229. *
  230. * @param integer|array(integer,integer) $offset
  231. * @throws \OutOfBoundsException
  232. */
  233. private function validateOffset($offset) {
  234. if (is_array($offset)) {
  235. $this->validateBitOffset($offset);
  236. } else {
  237. if ($offset < 0 || $offset >= $this->_length) {
  238. throw new \OutOfBoundsException(
  239. sprintf(
  240. 'Maximum byte index is "%d". Got "%d".', $this->_length - 1, $offset
  241. )
  242. );
  243. }
  244. }
  245. }
  246. /**
  247. * Validate the given offset is usable as bit offset
  248. *
  249. * @param array(integer,integer) $offset
  250. * @throws \OutOfBoundsException
  251. */
  252. private function validateBitOffset(array $offset) {
  253. if (count($offset) != 2) {
  254. throw new \OutOfBoundsException(
  255. sprintf(
  256. 'Bit index needs two elements (byte and bit index). Got "%d".', count($offset)
  257. )
  258. );
  259. }
  260. $this->validateOffset($offset[0]);
  261. if ($offset[1] < 0 || $offset[1] >= 8) {
  262. throw new \OutOfBoundsException(
  263. sprintf(
  264. 'Maximum bit index is "7". Got "%d".', $offset[1]
  265. )
  266. );
  267. }
  268. }
  269. /**
  270. * Validate the given value is a storeable in a single byte
  271. *
  272. * @param integer $offset
  273. * @throws \OutOfBoundsException
  274. */
  275. private function validateValue($value) {
  276. if (($value < 0 || $value > 255)) {
  277. throw new \OutOfRangeException(
  278. sprintf('Byte value expected (0-255). Got "%d"', $value)
  279. );
  280. }
  281. }
  282. /**
  283. * Allow to iterate the bytes
  284. * @return \Iterator
  285. */
  286. public function getIterator() {
  287. return new \ArrayIterator($this->_bytes);
  288. }
  289. /**
  290. * Return byte count
  291. *
  292. * @return Integer
  293. */
  294. public function count() {
  295. return count($this->_bytes);
  296. }
  297. public static function createFromHex($hexString) {
  298. $bytes = new ByteArray();
  299. $bytes->fromHexString($hexString, TRUE);
  300. return $bytes;
  301. }
  302. }
  303. }