FrameTest.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. <?php
  2. namespace Ratchet\RFC6455\Test\Unit\Messaging;
  3. use Ratchet\RFC6455\Messaging\Frame;
  4. use PHPUnit\Framework\TestCase;
  5. /**
  6. * @covers Ratchet\RFC6455\Messaging\Frame
  7. * @todo getMaskingKey, getPayloadStartingByte don't have tests yet
  8. * @todo Could use some clean up in general, I had to rush to fix a bug for a deadline, sorry.
  9. */
  10. class FrameTest extends TestCase {
  11. protected $_firstByteFinText = '10000001';
  12. protected $_secondByteMaskedSPL = '11111101';
  13. /** @var Frame */
  14. protected $_frame;
  15. protected $_packer;
  16. public function setUp() {
  17. $this->_frame = new Frame;
  18. }
  19. /**
  20. * Encode the fake binary string to send over the wire
  21. * @param string of 1's and 0's
  22. * @return string
  23. */
  24. public static function encode($in) {
  25. if (strlen($in) > 8) {
  26. $out = '';
  27. while (strlen($in) >= 8) {
  28. $out .= static::encode(substr($in, 0, 8));
  29. $in = substr($in, 8);
  30. }
  31. return $out;
  32. }
  33. return chr(bindec($in));
  34. }
  35. /**
  36. * This is a data provider
  37. * param string The UTF8 message
  38. * param string The WebSocket framed message, then base64_encoded
  39. */
  40. public static function UnframeMessageProvider() {
  41. return array(
  42. array('Hello World!', 'gYydAIfa1WXrtvIg0LXvbOP7'),
  43. array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg'),
  44. array('ಠ_ಠ', 'gYfnSpu5B/g75gf4Ow=='),
  45. array(
  46. "The quick brown fox jumps over the lazy dog. All work and no play makes Chris a dull boy. I'm trying to get past 128 characters for a unit test here...",
  47. 'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY='
  48. )
  49. );
  50. }
  51. public static function underflowProvider() {
  52. return array(
  53. array('isFinal', ''),
  54. array('getRsv1', ''),
  55. array('getRsv2', ''),
  56. array('getRsv3', ''),
  57. array('getOpcode', ''),
  58. array('isMasked', '10000001'),
  59. array('getPayloadLength', '10000001'),
  60. array('getPayloadLength', '1000000111111110'),
  61. array('getMaskingKey', '1000000110000111'),
  62. array('getPayload', '100000011000000100011100101010101001100111110100')
  63. );
  64. }
  65. /**
  66. * @dataProvider underflowProvider
  67. *
  68. * @covers Ratchet\RFC6455\Messaging\Frame::isFinal
  69. * @covers Ratchet\RFC6455\Messaging\Frame::getRsv1
  70. * @covers Ratchet\RFC6455\Messaging\Frame::getRsv2
  71. * @covers Ratchet\RFC6455\Messaging\Frame::getRsv3
  72. * @covers Ratchet\RFC6455\Messaging\Frame::getOpcode
  73. * @covers Ratchet\RFC6455\Messaging\Frame::isMasked
  74. * @covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
  75. * @covers Ratchet\RFC6455\Messaging\Frame::getMaskingKey
  76. * @covers Ratchet\RFC6455\Messaging\Frame::getPayload
  77. */
  78. public function testUnderflowExceptionFromAllTheMethodsMimickingBuffering($method, $bin) {
  79. $this->setExpectedException('\UnderflowException');
  80. if (!empty($bin)) {
  81. $this->_frame->addBuffer(static::encode($bin));
  82. }
  83. call_user_func(array($this->_frame, $method));
  84. }
  85. /**
  86. * A data provider for testing the first byte of a WebSocket frame
  87. * param bool Given, is the byte indicate this is the final frame
  88. * param int Given, what is the expected opcode
  89. * param string of 0|1 Each character represents a bit in the byte
  90. */
  91. public static function firstByteProvider() {
  92. return array(
  93. array(false, false, false, true, 8, '00011000'),
  94. array(true, false, true, false, 10, '10101010'),
  95. array(false, false, false, false, 15, '00001111'),
  96. array(true, false, false, false, 1, '10000001'),
  97. array(true, true, true, true, 15, '11111111'),
  98. array(true, true, false, false, 7, '11000111')
  99. );
  100. }
  101. /**
  102. * @dataProvider firstByteProvider
  103. * covers Ratchet\RFC6455\Messaging\Frame::isFinal
  104. */
  105. public function testFinCodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) {
  106. $this->_frame->addBuffer(static::encode($bin));
  107. $this->assertEquals($fin, $this->_frame->isFinal());
  108. }
  109. /**
  110. * @dataProvider firstByteProvider
  111. * covers Ratchet\RFC6455\Messaging\Frame::getRsv1
  112. * covers Ratchet\RFC6455\Messaging\Frame::getRsv2
  113. * covers Ratchet\RFC6455\Messaging\Frame::getRsv3
  114. */
  115. public function testGetRsvFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) {
  116. $this->_frame->addBuffer(static::encode($bin));
  117. $this->assertEquals($rsv1, $this->_frame->getRsv1());
  118. $this->assertEquals($rsv2, $this->_frame->getRsv2());
  119. $this->assertEquals($rsv3, $this->_frame->getRsv3());
  120. }
  121. /**
  122. * @dataProvider firstByteProvider
  123. * covers Ratchet\RFC6455\Messaging\Frame::getOpcode
  124. */
  125. public function testOpcodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) {
  126. $this->_frame->addBuffer(static::encode($bin));
  127. $this->assertEquals($opcode, $this->_frame->getOpcode());
  128. }
  129. /**
  130. * @dataProvider UnframeMessageProvider
  131. * covers Ratchet\RFC6455\Messaging\Frame::isFinal
  132. */
  133. public function testFinCodeFromFullMessage($msg, $encoded) {
  134. $this->_frame->addBuffer(base64_decode($encoded));
  135. $this->assertTrue($this->_frame->isFinal());
  136. }
  137. /**
  138. * @dataProvider UnframeMessageProvider
  139. * covers Ratchet\RFC6455\Messaging\Frame::getOpcode
  140. */
  141. public function testOpcodeFromFullMessage($msg, $encoded) {
  142. $this->_frame->addBuffer(base64_decode($encoded));
  143. $this->assertEquals(1, $this->_frame->getOpcode());
  144. }
  145. public static function payloadLengthDescriptionProvider() {
  146. return array(
  147. array(7, '01110101'),
  148. array(7, '01111101'),
  149. array(23, '01111110'),
  150. array(71, '01111111'),
  151. array(7, '00000000'), // Should this throw an exception? Can a payload be empty?
  152. array(7, '00000001')
  153. );
  154. }
  155. /**
  156. * @dataProvider payloadLengthDescriptionProvider
  157. * covers Ratchet\RFC6455\Messaging\Frame::addBuffer
  158. * covers Ratchet\RFC6455\Messaging\Frame::getFirstPayloadVal
  159. */
  160. public function testFirstPayloadDesignationValue($bits, $bin) {
  161. $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
  162. $this->_frame->addBuffer(static::encode($bin));
  163. $ref = new \ReflectionClass($this->_frame);
  164. $cb = $ref->getMethod('getFirstPayloadVal');
  165. $cb->setAccessible(true);
  166. $this->assertEquals(bindec($bin), $cb->invoke($this->_frame));
  167. }
  168. /**
  169. * covers Ratchet\RFC6455\Messaging\Frame::getFirstPayloadVal
  170. */
  171. public function testFirstPayloadValUnderflow() {
  172. $ref = new \ReflectionClass($this->_frame);
  173. $cb = $ref->getMethod('getFirstPayloadVal');
  174. $cb->setAccessible(true);
  175. $this->setExpectedException('UnderflowException');
  176. $cb->invoke($this->_frame);
  177. }
  178. /**
  179. * @dataProvider payloadLengthDescriptionProvider
  180. * covers Ratchet\RFC6455\Messaging\Frame::getNumPayloadBits
  181. */
  182. public function testDetermineHowManyBitsAreUsedToDescribePayload($expected_bits, $bin) {
  183. $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
  184. $this->_frame->addBuffer(static::encode($bin));
  185. $ref = new \ReflectionClass($this->_frame);
  186. $cb = $ref->getMethod('getNumPayloadBits');
  187. $cb->setAccessible(true);
  188. $this->assertEquals($expected_bits, $cb->invoke($this->_frame));
  189. }
  190. /**
  191. * covers Ratchet\RFC6455\Messaging\Frame::getNumPayloadBits
  192. */
  193. public function testgetNumPayloadBitsUnderflow() {
  194. $ref = new \ReflectionClass($this->_frame);
  195. $cb = $ref->getMethod('getNumPayloadBits');
  196. $cb->setAccessible(true);
  197. $this->setExpectedException('UnderflowException');
  198. $cb->invoke($this->_frame);
  199. }
  200. public function secondByteProvider() {
  201. return array(
  202. array(true, 1, '10000001'),
  203. array(false, 1, '00000001'),
  204. array(true, 125, $this->_secondByteMaskedSPL)
  205. );
  206. }
  207. /**
  208. * @dataProvider secondByteProvider
  209. * covers Ratchet\RFC6455\Messaging\Frame::isMasked
  210. */
  211. public function testIsMaskedReturnsExpectedValue($masked, $payload_length, $bin) {
  212. $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
  213. $this->_frame->addBuffer(static::encode($bin));
  214. $this->assertEquals($masked, $this->_frame->isMasked());
  215. }
  216. /**
  217. * @dataProvider UnframeMessageProvider
  218. * covers Ratchet\RFC6455\Messaging\Frame::isMasked
  219. */
  220. public function testIsMaskedFromFullMessage($msg, $encoded) {
  221. $this->_frame->addBuffer(base64_decode($encoded));
  222. $this->assertTrue($this->_frame->isMasked());
  223. }
  224. /**
  225. * @dataProvider secondByteProvider
  226. * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
  227. */
  228. public function testGetPayloadLengthWhenOnlyFirstFrameIsUsed($masked, $payload_length, $bin) {
  229. $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
  230. $this->_frame->addBuffer(static::encode($bin));
  231. $this->assertEquals($payload_length, $this->_frame->getPayloadLength());
  232. }
  233. /**
  234. * @dataProvider UnframeMessageProvider
  235. * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
  236. * @todo Not yet testing when second additional payload length descriptor
  237. */
  238. public function testGetPayloadLengthFromFullMessage($msg, $encoded) {
  239. $this->_frame->addBuffer(base64_decode($encoded));
  240. $this->assertEquals(strlen($msg), $this->_frame->getPayloadLength());
  241. }
  242. public function maskingKeyProvider() {
  243. $frame = new Frame;
  244. return array(
  245. array($frame->generateMaskingKey()),
  246. array($frame->generateMaskingKey()),
  247. array($frame->generateMaskingKey())
  248. );
  249. }
  250. /**
  251. * @dataProvider maskingKeyProvider
  252. * covers Ratchet\RFC6455\Messaging\Frame::getMaskingKey
  253. * @todo I I wrote the dataProvider incorrectly, skipping for now
  254. */
  255. public function testGetMaskingKey($mask) {
  256. $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
  257. $this->_frame->addBuffer(static::encode($this->_secondByteMaskedSPL));
  258. $this->_frame->addBuffer($mask);
  259. $this->assertEquals($mask, $this->_frame->getMaskingKey());
  260. }
  261. /**
  262. * covers Ratchet\RFC6455\Messaging\Frame::getMaskingKey
  263. */
  264. public function testGetMaskingKeyOnUnmaskedPayload() {
  265. $frame = new Frame('Hello World!');
  266. $this->assertEquals('', $frame->getMaskingKey());
  267. }
  268. /**
  269. * @dataProvider UnframeMessageProvider
  270. * covers Ratchet\RFC6455\Messaging\Frame::getPayload
  271. * @todo Move this test to bottom as it requires all methods of the class
  272. */
  273. public function testUnframeFullMessage($unframed, $base_framed) {
  274. $this->_frame->addBuffer(base64_decode($base_framed));
  275. $this->assertEquals($unframed, $this->_frame->getPayload());
  276. }
  277. public static function messageFragmentProvider() {
  278. return array(
  279. array(false, '', '', '', '', '')
  280. );
  281. }
  282. /**
  283. * @dataProvider UnframeMessageProvider
  284. * covers Ratchet\RFC6455\Messaging\Frame::getPayload
  285. */
  286. public function testCheckPiecingTogetherMessage($msg, $encoded) {
  287. $framed = base64_decode($encoded);
  288. for ($i = 0, $len = strlen($framed);$i < $len; $i++) {
  289. $this->_frame->addBuffer(substr($framed, $i, 1));
  290. }
  291. $this->assertEquals($msg, $this->_frame->getPayload());
  292. }
  293. /**
  294. * covers Ratchet\RFC6455\Messaging\Frame::__construct
  295. * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
  296. * covers Ratchet\RFC6455\Messaging\Frame::getPayload
  297. */
  298. public function testLongCreate() {
  299. $len = 65525;
  300. $pl = $this->generateRandomString($len);
  301. $frame = new Frame($pl, true, Frame::OP_PING);
  302. $this->assertTrue($frame->isFinal());
  303. $this->assertEquals(Frame::OP_PING, $frame->getOpcode());
  304. $this->assertFalse($frame->isMasked());
  305. $this->assertEquals($len, $frame->getPayloadLength());
  306. $this->assertEquals($pl, $frame->getPayload());
  307. }
  308. /**
  309. * covers Ratchet\RFC6455\Messaging\Frame::__construct
  310. * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
  311. */
  312. public function testReallyLongCreate() {
  313. $len = 65575;
  314. $frame = new Frame($this->generateRandomString($len));
  315. $this->assertEquals($len, $frame->getPayloadLength());
  316. }
  317. /**
  318. * covers Ratchet\RFC6455\Messaging\Frame::__construct
  319. * covers Ratchet\RFC6455\Messaging\Frame::extractOverflow
  320. */
  321. public function testExtractOverflow() {
  322. $string1 = $this->generateRandomString();
  323. $frame1 = new Frame($string1);
  324. $string2 = $this->generateRandomString();
  325. $frame2 = new Frame($string2);
  326. $cat = new Frame;
  327. $cat->addBuffer($frame1->getContents() . $frame2->getContents());
  328. $this->assertEquals($frame1->getContents(), $cat->getContents());
  329. $this->assertEquals($string1, $cat->getPayload());
  330. $uncat = new Frame;
  331. $uncat->addBuffer($cat->extractOverflow());
  332. $this->assertEquals($string1, $cat->getPayload());
  333. $this->assertEquals($string2, $uncat->getPayload());
  334. }
  335. /**
  336. * covers Ratchet\RFC6455\Messaging\Frame::extractOverflow
  337. */
  338. public function testEmptyExtractOverflow() {
  339. $string = $this->generateRandomString();
  340. $frame = new Frame($string);
  341. $this->assertEquals($string, $frame->getPayload());
  342. $this->assertEquals('', $frame->extractOverflow());
  343. $this->assertEquals($string, $frame->getPayload());
  344. }
  345. /**
  346. * covers Ratchet\RFC6455\Messaging\Frame::getContents
  347. */
  348. public function testGetContents() {
  349. $msg = 'The quick brown fox jumps over the lazy dog.';
  350. $frame1 = new Frame($msg);
  351. $frame2 = new Frame($msg);
  352. $frame2->maskPayload();
  353. $this->assertNotEquals($frame1->getContents(), $frame2->getContents());
  354. $this->assertEquals(strlen($frame1->getContents()) + 4, strlen($frame2->getContents()));
  355. }
  356. /**
  357. * covers Ratchet\RFC6455\Messaging\Frame::maskPayload
  358. */
  359. public function testMasking() {
  360. $msg = 'The quick brown fox jumps over the lazy dog.';
  361. $frame = new Frame($msg);
  362. $frame->maskPayload();
  363. $this->assertTrue($frame->isMasked());
  364. $this->assertEquals($msg, $frame->getPayload());
  365. }
  366. /**
  367. * covers Ratchet\RFC6455\Messaging\Frame::unMaskPayload
  368. */
  369. public function testUnMaskPayload() {
  370. $string = $this->generateRandomString();
  371. $frame = new Frame($string);
  372. $frame->maskPayload()->unMaskPayload();
  373. $this->assertFalse($frame->isMasked());
  374. $this->assertEquals($string, $frame->getPayload());
  375. }
  376. /**
  377. * covers Ratchet\RFC6455\Messaging\Frame::generateMaskingKey
  378. */
  379. public function testGenerateMaskingKey() {
  380. $dupe = false;
  381. $done = array();
  382. for ($i = 0; $i < 10; $i++) {
  383. $new = $this->_frame->generateMaskingKey();
  384. if (in_array($new, $done)) {
  385. $dupe = true;
  386. }
  387. $done[] = $new;
  388. }
  389. $this->assertEquals(4, strlen($new));
  390. $this->assertFalse($dupe);
  391. }
  392. /**
  393. * covers Ratchet\RFC6455\Messaging\Frame::maskPayload
  394. */
  395. public function testGivenMaskIsValid() {
  396. $this->setExpectedException('InvalidArgumentException');
  397. $this->_frame->maskPayload('hello world');
  398. }
  399. /**
  400. * covers Ratchet\RFC6455\Messaging\Frame::maskPayload
  401. */
  402. public function testGivenMaskIsValidAscii() {
  403. if (!extension_loaded('mbstring')) {
  404. $this->markTestSkipped("mbstring required for this test");
  405. return;
  406. }
  407. $this->setExpectedException('OutOfBoundsException');
  408. $this->_frame->maskPayload('x✖');
  409. }
  410. protected function generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) {
  411. $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&/()=[]{}'; // ยง
  412. $useChars = array();
  413. for($i = 0; $i < $length; $i++) {
  414. $useChars[] = $characters[mt_rand(0, strlen($characters) - 1)];
  415. }
  416. if($addSpaces === true) {
  417. array_push($useChars, ' ', ' ', ' ', ' ', ' ', ' ');
  418. }
  419. if($addNumbers === true) {
  420. array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9));
  421. }
  422. shuffle($useChars);
  423. $randomString = trim(implode('', $useChars));
  424. $randomString = substr($randomString, 0, $length);
  425. return $randomString;
  426. }
  427. /**
  428. * There was a frame boundary issue when the first 3 bytes of a frame with a payload greater than
  429. * 126 was added to the frame buffer and then Frame::getPayloadLength was called. It would cause the frame
  430. * to set the payload length to 126 and then not recalculate it once the full length information was available.
  431. *
  432. * This is fixed by setting the defPayLen back to -1 before the underflow exception is thrown.
  433. *
  434. * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
  435. * covers Ratchet\RFC6455\Messaging\Frame::extractOverflow
  436. */
  437. public function testFrameDeliveredOneByteAtATime() {
  438. $startHeader = "\x01\x7e\x01\x00"; // header for a text frame of 256 - non-final
  439. $framePayload = str_repeat("*", 256);
  440. $rawOverflow = "xyz";
  441. $rawFrame = $startHeader . $framePayload . $rawOverflow;
  442. $frame = new Frame();
  443. $payloadLen = 256;
  444. for ($i = 0; $i < strlen($rawFrame); $i++) {
  445. $frame->addBuffer($rawFrame[$i]);
  446. try {
  447. // payloadLen will
  448. $payloadLen = $frame->getPayloadLength();
  449. } catch (\UnderflowException $e) {
  450. if ($i > 2) { // we should get an underflow on 0,1,2
  451. $this->fail("Underflow exception when the frame length should be available");
  452. }
  453. }
  454. if ($payloadLen !== 256) {
  455. $this->fail("Payload length of " . $payloadLen . " should have been 256.");
  456. }
  457. }
  458. // make sure the overflow is good
  459. $this->assertEquals($rawOverflow, $frame->extractOverflow());
  460. }
  461. }