PageRenderTime 43ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Wrench/Frame/HybiFrame.php

https://github.com/tinkajts/php-websocket
PHP | 376 lines | 215 code | 49 blank | 112 comment | 29 complexity | 385af4940f49ac7ddbaf196805cf8232 MD5 | raw file
Possible License(s): WTFPL
  1. <?php
  2. namespace Wrench\Frame;
  3. use Wrench\Protocol\Protocol;
  4. use Wrench\Exception\FrameException;
  5. use \InvalidArgumentException;
  6. class HybiFrame extends Frame
  7. {
  8. // First byte
  9. const BITFIELD_FINAL = 0x80;
  10. const BITFIELD_RSV1 = 0x40;
  11. const BITFIELD_RSV2 = 0x20;
  12. const BITFIELD_RSV3 = 0x10;
  13. const BITFIELD_TYPE = 0x0f;
  14. // Second byte
  15. const BITFIELD_MASKED = 0x80;
  16. const BITFIELD_INITIAL_LENGTH = 0x7f;
  17. // The inital byte offset before
  18. const BYTE_HEADER = 0;
  19. const BYTE_MASKED = 1;
  20. const BYTE_INITIAL_LENGTH = 1;
  21. /**
  22. * Whether the payload is masked
  23. *
  24. * @var boolean
  25. */
  26. protected $masked = null;
  27. /**
  28. * Masking key
  29. *
  30. * @var string
  31. */
  32. protected $mask = null;
  33. /**
  34. * Byte offsets
  35. *
  36. * @var int
  37. */
  38. protected $offset_payload = null;
  39. protected $offset_mask = null;
  40. /**
  41. * @see Wrench\Frame.Frame::encode()
  42. * ws-frame = frame-fin ; 1 bit in length
  43. * frame-rsv1 ; 1 bit in length
  44. * frame-rsv2 ; 1 bit in length
  45. * frame-rsv3 ; 1 bit in length
  46. * frame-opcode ; 4 bits in length
  47. * frame-masked ; 1 bit in length
  48. * frame-payload-length ; either 7, 7+16,
  49. * ; or 7+64 bits in
  50. * ; length
  51. * [ frame-masking-key ] ; 32 bits in length
  52. * frame-payload-data ; n*8 bits in
  53. * ; length, where
  54. * ; n >= 0
  55. */
  56. public function encode($payload, $type = Protocol::TYPE_TEXT, $masked = false)
  57. {
  58. if (!is_int($type) || !in_array($type, Protocol::$frameTypes)) {
  59. throw new InvalidArgumentException('Invalid frame type');
  60. }
  61. $this->type = $type;
  62. $this->masked = $masked;
  63. $this->payload = $payload;
  64. $this->length = strlen($this->payload);
  65. $this->offset_mask = null;
  66. $this->offset_payload = null;
  67. $this->buffer = "\x00\x00";
  68. $this->buffer[self::BYTE_HEADER] = chr(
  69. (self::BITFIELD_TYPE & $this->type)
  70. | (self::BITFIELD_FINAL & PHP_INT_MAX)
  71. );
  72. $masked_bit = (self::BITFIELD_MASKED & ($this->masked ? PHP_INT_MAX : 0));
  73. if ($this->length <= 125) {
  74. $this->buffer[self::BYTE_INITIAL_LENGTH] = chr(
  75. (self::BITFIELD_INITIAL_LENGTH & $this->length) | $masked_bit
  76. );
  77. } elseif ($this->length <= 65536) {
  78. $this->buffer[self::BYTE_INITIAL_LENGTH] = chr(
  79. (self::BITFIELD_INITIAL_LENGTH & 126) | $masked_bit
  80. );
  81. $this->buffer .= pack('n', $this->length);
  82. } else {
  83. $this->buffer[self::BYTE_INITIAL_LENGTH] = chr(
  84. (self::BITFIELD_INITIAL_LENGTH & 127) | $masked_bit
  85. );
  86. if (PHP_INT_MAX > 2147483647) {
  87. $this->buffer .= pack('NN', $this->length >> 32, $this->length);
  88. // $this->buffer .= pack('I', $this->length);
  89. } else {
  90. $this->buffer .= pack('NN', 0, $this->length);
  91. }
  92. }
  93. if ($this->masked) {
  94. $this->mask = $this->generateMask();
  95. $this->buffer .= $this->mask;
  96. $this->buffer .= $this->mask($this->payload);
  97. } else {
  98. $this->buffer .= $this->payload;
  99. }
  100. $this->offset_mask = $this->getMaskOffset();
  101. $this->offset_payload = $this->getPayloadOffset();
  102. return $this;
  103. }
  104. /**
  105. * Masks/Unmasks the frame
  106. *
  107. * @param string $payload
  108. * @return string
  109. */
  110. protected function mask($payload)
  111. {
  112. $length = strlen($payload);
  113. $mask = $this->getMask();
  114. $unmasked = '';
  115. for ($i = 0; $i < $length; $i++) {
  116. $unmasked .= $payload[$i] ^ $mask[$i % 4];
  117. }
  118. return $unmasked;
  119. }
  120. /**
  121. * Masks a payload
  122. *
  123. * @param string $payload
  124. * @return string
  125. */
  126. protected function unmask($payload)
  127. {
  128. return $this->mask($payload);
  129. }
  130. public function receiveData($data)
  131. {
  132. if ($this->getBufferLength() <= self::BYTE_INITIAL_LENGTH) {
  133. $this->length = null;
  134. $this->offset_payload = null;
  135. }
  136. parent::receiveData($data);
  137. }
  138. /**
  139. * Gets the mask
  140. *
  141. * @throws FrameException
  142. * @return string
  143. */
  144. protected function getMask()
  145. {
  146. if (!isset($this->mask)) {
  147. if (!$this->isMasked()) {
  148. throw new FrameException('Cannot get mask: frame is not masked');
  149. }
  150. $this->mask = substr($this->buffer, $this->getMaskOffset(), $this->getMaskSize());
  151. }
  152. return $this->mask;
  153. }
  154. /**
  155. * Generates a suitable masking key
  156. *
  157. * @return string
  158. */
  159. protected function generateMask()
  160. {
  161. if (extension_loaded('openssl')) {
  162. return openssl_random_pseudo_bytes(4);
  163. } else {
  164. // SHA1 is 128 bit (= 16 bytes)
  165. // So we pack it into 32 bits
  166. return pack('N', sha1(spl_object_hash($this) . mt_rand(0, PHP_INT_MAX) . uniqid('', true), true));
  167. }
  168. }
  169. /**
  170. * Whether the frame is masked
  171. *
  172. * @return boolean
  173. */
  174. public function isMasked()
  175. {
  176. if (!isset($this->masked)) {
  177. if (!isset($this->buffer[1])) {
  178. throw new FrameException('Cannot tell if frame is masked: not enough frame data recieved');
  179. }
  180. $this->masked = (boolean)(ord($this->buffer[1]) & self::BITFIELD_MASKED);
  181. }
  182. return $this->masked;
  183. }
  184. /**
  185. * @see Wrench\Frame.Frame::getExpectedDataLength()
  186. */
  187. protected function getExpectedBufferLength()
  188. {
  189. return $this->getLength() + $this->getPayloadOffset();
  190. }
  191. /**
  192. * Gets the offset of the payload in the frame
  193. *
  194. * @return int
  195. */
  196. protected function getPayloadOffset()
  197. {
  198. if (!isset($this->offset_payload)) {
  199. $offset = $this->getMaskOffset();
  200. $offset += $this->getMaskSize();
  201. $this->offset_payload = $offset;
  202. }
  203. return $this->offset_payload;
  204. }
  205. /**
  206. * Gets the offset in the frame to the masking bytes
  207. *
  208. * @return int
  209. */
  210. protected function getMaskOffset()
  211. {
  212. if (!isset($this->offset_mask)) {
  213. $offset = self::BYTE_INITIAL_LENGTH + 1;
  214. $offset += $this->getLengthSize();
  215. $this->offset_mask = $offset;
  216. }
  217. return $this->offset_mask;
  218. }
  219. /**
  220. * @see Wrench\Frame.Frame::getLength()
  221. */
  222. public function getLength()
  223. {
  224. if (!$this->length) {
  225. $initial = $this->getInitialLength();
  226. if ($initial < 126) {
  227. $this->length = $initial;
  228. } elseif ($initial >= 126) {
  229. // Extended payload length: 2 or 8 bytes
  230. $start = self::BYTE_INITIAL_LENGTH + 1;
  231. $end = self::BYTE_INITIAL_LENGTH + $this->getLengthSize();
  232. if ($end > $this->getBufferLength()) {
  233. throw new FrameException('Cannot get extended length: need more data');
  234. }
  235. $length = 0;
  236. for ($i = $start; $i <= $end; $i++) {
  237. $length <<= 8;
  238. $length += ord($this->buffer[$i]);
  239. }
  240. $this->length = $length;
  241. }
  242. }
  243. return $this->length;
  244. }
  245. /**
  246. * Gets the inital length value, stored in the first length byte
  247. *
  248. * This determines how the rest of the length value is parsed out of the
  249. * frame.
  250. *
  251. * @return int
  252. */
  253. protected function getInitialLength()
  254. {
  255. if (!isset($this->buffer[self::BYTE_INITIAL_LENGTH])) {
  256. throw new FrameException('Cannot yet tell expected length');
  257. }
  258. $a = (int)(ord($this->buffer[self::BYTE_INITIAL_LENGTH]) & self::BITFIELD_INITIAL_LENGTH);
  259. return (int)(ord($this->buffer[self::BYTE_INITIAL_LENGTH]) & self::BITFIELD_INITIAL_LENGTH);
  260. }
  261. /**
  262. * Returns the byte size of the length part of the frame
  263. *
  264. * Not including the initial 7 bit part
  265. *
  266. * @return int
  267. */
  268. protected function getLengthSize()
  269. {
  270. $initial = $this->getInitialLength();
  271. if ($initial < 126) {
  272. return 0;
  273. } elseif ($initial === 126) {
  274. return 2;
  275. } elseif ($initial === 127) {
  276. return 8;
  277. }
  278. }
  279. /**
  280. * Returns the byte size of the mask part of the frame
  281. *
  282. * @return int
  283. */
  284. protected function getMaskSize()
  285. {
  286. if ($this->isMasked()) {
  287. return 4;
  288. }
  289. return 0;
  290. }
  291. /**
  292. * @see Wrench\Frame.Frame::decodeFramePayloadFromBuffer()
  293. */
  294. protected function decodeFramePayloadFromBuffer()
  295. {
  296. $payload = substr($this->buffer, $this->getPayloadOffset());
  297. if ($this->isMasked()) {
  298. $payload = $this->unmask($payload);
  299. }
  300. $this->payload = $payload;
  301. }
  302. /**
  303. * @see Wrench\Frame.Frame::isFinal()
  304. */
  305. public function isFinal()
  306. {
  307. if (!isset($this->buffer[self::BYTE_HEADER])) {
  308. throw new FrameException('Cannot yet tell if frame is final');
  309. }
  310. return (boolean)(ord($this->buffer[self::BYTE_HEADER]) & self::BITFIELD_FINAL);
  311. }
  312. /**
  313. * @throws FrameException
  314. * @see Wrench\Frame.Frame::getType()
  315. */
  316. public function getType()
  317. {
  318. if (!isset($this->buffer[self::BYTE_HEADER])) {
  319. throw new FrameException('Cannot yet tell type of frame');
  320. }
  321. $type = (int)(ord($this->buffer[self::BYTE_HEADER]) & self::BITFIELD_TYPE);
  322. if (!in_array($type, Protocol::$frameTypes)) {
  323. throw new FrameException('Invalid payload type');
  324. }
  325. return $type;
  326. }
  327. }