MessageBufferTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. <?php
  2. namespace Ratchet\RFC6455\Test\Unit\Messaging;
  3. use Ratchet\RFC6455\Messaging\CloseFrameChecker;
  4. use Ratchet\RFC6455\Messaging\Frame;
  5. use Ratchet\RFC6455\Messaging\Message;
  6. use Ratchet\RFC6455\Messaging\MessageBuffer;
  7. use React\EventLoop\Factory;
  8. use PHPUnit\Framework\TestCase;
  9. class MessageBufferTest extends TestCase
  10. {
  11. /**
  12. * This is to test that MessageBuffer can handle a large receive
  13. * buffer with many many frames without blowing the stack (pre-v0.4 issue)
  14. */
  15. public function testProcessingLotsOfFramesInASingleChunk() {
  16. $frame = new Frame('a', true, Frame::OP_TEXT);
  17. $frameRaw = $frame->getContents();
  18. $data = str_repeat($frameRaw, 1000);
  19. $messageCount = 0;
  20. $messageBuffer = new MessageBuffer(
  21. new CloseFrameChecker(),
  22. function (Message $message) use (&$messageCount) {
  23. $messageCount++;
  24. $this->assertEquals('a', $message->getPayload());
  25. },
  26. null,
  27. false
  28. );
  29. $messageBuffer->onData($data);
  30. $this->assertEquals(1000, $messageCount);
  31. }
  32. public function testProcessingMessagesAsynchronouslyWhileBlockingInMessageHandler() {
  33. $loop = Factory::create();
  34. $frameA = new Frame('a', true, Frame::OP_TEXT);
  35. $frameB = new Frame('b', true, Frame::OP_TEXT);
  36. $bReceived = false;
  37. $messageBuffer = new MessageBuffer(
  38. new CloseFrameChecker(),
  39. function (Message $message) use (&$messageCount, &$bReceived, $loop) {
  40. $payload = $message->getPayload();
  41. $bReceived = $payload === 'b';
  42. if (!$bReceived) {
  43. $loop->run();
  44. }
  45. },
  46. null,
  47. false
  48. );
  49. $loop->addPeriodicTimer(0.1, function () use ($messageBuffer, $frameB, $loop) {
  50. $loop->stop();
  51. $messageBuffer->onData($frameB->getContents());
  52. });
  53. $messageBuffer->onData($frameA->getContents());
  54. $this->assertTrue($bReceived);
  55. }
  56. public function testInvalidFrameLength() {
  57. $frame = new Frame(str_repeat('a', 200), true, Frame::OP_TEXT);
  58. $frameRaw = $frame->getContents();
  59. $frameRaw[1] = "\x7f"; // 127 in the first spot
  60. $frameRaw[2] = "\xff"; // this will unpack to -1
  61. $frameRaw[3] = "\xff";
  62. $frameRaw[4] = "\xff";
  63. $frameRaw[5] = "\xff";
  64. $frameRaw[6] = "\xff";
  65. $frameRaw[7] = "\xff";
  66. $frameRaw[8] = "\xff";
  67. $frameRaw[9] = "\xff";
  68. /** @var Frame $controlFrame */
  69. $controlFrame = null;
  70. $messageCount = 0;
  71. $messageBuffer = new MessageBuffer(
  72. new CloseFrameChecker(),
  73. function (Message $message) use (&$messageCount) {
  74. $messageCount++;
  75. },
  76. function (Frame $frame) use (&$controlFrame) {
  77. $this->assertNull($controlFrame);
  78. $controlFrame = $frame;
  79. },
  80. false,
  81. null,
  82. 0,
  83. 10
  84. );
  85. $messageBuffer->onData($frameRaw);
  86. $this->assertEquals(0, $messageCount);
  87. $this->assertTrue($controlFrame instanceof Frame);
  88. $this->assertEquals(Frame::OP_CLOSE, $controlFrame->getOpcode());
  89. $this->assertEquals([Frame::CLOSE_PROTOCOL], array_merge(unpack('n*', substr($controlFrame->getPayload(), 0, 2))));
  90. }
  91. public function testFrameLengthTooBig() {
  92. $frame = new Frame(str_repeat('a', 200), true, Frame::OP_TEXT);
  93. $frameRaw = $frame->getContents();
  94. $frameRaw[1] = "\x7f"; // 127 in the first spot
  95. $frameRaw[2] = "\x7f"; // this will unpack to -1
  96. $frameRaw[3] = "\xff";
  97. $frameRaw[4] = "\xff";
  98. $frameRaw[5] = "\xff";
  99. $frameRaw[6] = "\xff";
  100. $frameRaw[7] = "\xff";
  101. $frameRaw[8] = "\xff";
  102. $frameRaw[9] = "\xff";
  103. /** @var Frame $controlFrame */
  104. $controlFrame = null;
  105. $messageCount = 0;
  106. $messageBuffer = new MessageBuffer(
  107. new CloseFrameChecker(),
  108. function (Message $message) use (&$messageCount) {
  109. $messageCount++;
  110. },
  111. function (Frame $frame) use (&$controlFrame) {
  112. $this->assertNull($controlFrame);
  113. $controlFrame = $frame;
  114. },
  115. false,
  116. null,
  117. 0,
  118. 10
  119. );
  120. $messageBuffer->onData($frameRaw);
  121. $this->assertEquals(0, $messageCount);
  122. $this->assertTrue($controlFrame instanceof Frame);
  123. $this->assertEquals(Frame::OP_CLOSE, $controlFrame->getOpcode());
  124. $this->assertEquals([Frame::CLOSE_TOO_BIG], array_merge(unpack('n*', substr($controlFrame->getPayload(), 0, 2))));
  125. }
  126. public function testFrameLengthBiggerThanMaxMessagePayload() {
  127. $frame = new Frame(str_repeat('a', 200), true, Frame::OP_TEXT);
  128. $frameRaw = $frame->getContents();
  129. /** @var Frame $controlFrame */
  130. $controlFrame = null;
  131. $messageCount = 0;
  132. $messageBuffer = new MessageBuffer(
  133. new CloseFrameChecker(),
  134. function (Message $message) use (&$messageCount) {
  135. $messageCount++;
  136. },
  137. function (Frame $frame) use (&$controlFrame) {
  138. $this->assertNull($controlFrame);
  139. $controlFrame = $frame;
  140. },
  141. false,
  142. null,
  143. 100,
  144. 0
  145. );
  146. $messageBuffer->onData($frameRaw);
  147. $this->assertEquals(0, $messageCount);
  148. $this->assertTrue($controlFrame instanceof Frame);
  149. $this->assertEquals(Frame::OP_CLOSE, $controlFrame->getOpcode());
  150. $this->assertEquals([Frame::CLOSE_TOO_BIG], array_merge(unpack('n*', substr($controlFrame->getPayload(), 0, 2))));
  151. }
  152. public function testSecondFrameLengthPushesPastMaxMessagePayload() {
  153. $frame = new Frame(str_repeat('a', 200), false, Frame::OP_TEXT);
  154. $firstFrameRaw = $frame->getContents();
  155. $frame = new Frame(str_repeat('b', 200), true, Frame::OP_TEXT);
  156. $secondFrameRaw = $frame->getContents();
  157. /** @var Frame $controlFrame */
  158. $controlFrame = null;
  159. $messageCount = 0;
  160. $messageBuffer = new MessageBuffer(
  161. new CloseFrameChecker(),
  162. function (Message $message) use (&$messageCount) {
  163. $messageCount++;
  164. },
  165. function (Frame $frame) use (&$controlFrame) {
  166. $this->assertNull($controlFrame);
  167. $controlFrame = $frame;
  168. },
  169. false,
  170. null,
  171. 300,
  172. 0
  173. );
  174. $messageBuffer->onData($firstFrameRaw);
  175. // only put part of the second frame in to watch it fail fast
  176. $messageBuffer->onData(substr($secondFrameRaw, 0, 150));
  177. $this->assertEquals(0, $messageCount);
  178. $this->assertTrue($controlFrame instanceof Frame);
  179. $this->assertEquals(Frame::OP_CLOSE, $controlFrame->getOpcode());
  180. $this->assertEquals([Frame::CLOSE_TOO_BIG], array_merge(unpack('n*', substr($controlFrame->getPayload(), 0, 2))));
  181. }
  182. /**
  183. * Some test cases from memory limit inspired by https://github.com/BrandEmbassy/php-memory
  184. *
  185. * Here is the license for that project:
  186. * MIT License
  187. *
  188. * Copyright (c) 2018 Brand Embassy
  189. *
  190. * Permission is hereby granted, free of charge, to any person obtaining a copy
  191. * of this software and associated documentation files (the "Software"), to deal
  192. * in the Software without restriction, including without limitation the rights
  193. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  194. * copies of the Software, and to permit persons to whom the Software is
  195. * furnished to do so, subject to the following conditions:
  196. *
  197. * The above copyright notice and this permission notice shall be included in all
  198. * copies or substantial portions of the Software.
  199. *
  200. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  201. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  202. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  203. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  204. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  205. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  206. * SOFTWARE.
  207. */
  208. /**
  209. * @dataProvider phpConfigurationProvider
  210. *
  211. * @param string $phpConfigurationValue
  212. * @param int $expectedLimit
  213. */
  214. public function testMemoryLimits($phpConfigurationValue, $expectedLimit) {
  215. $method = new \ReflectionMethod('Ratchet\RFC6455\Messaging\MessageBuffer', 'getMemoryLimit');
  216. $method->setAccessible(true);
  217. $actualLimit = $method->invoke(null, $phpConfigurationValue);
  218. $this->assertSame($expectedLimit, $actualLimit);
  219. }
  220. public function phpConfigurationProvider() {
  221. return [
  222. 'without unit type, just bytes' => ['500', 500],
  223. '1 GB with big "G"' => ['1G', 1 * 1024 * 1024 * 1024],
  224. '128 MB with big "M"' => ['128M', 128 * 1024 * 1024],
  225. '128 MB with small "m"' => ['128m', 128 * 1024 * 1024],
  226. '24 kB with small "k"' => ['24k', 24 * 1024],
  227. '2 GB with small "g"' => ['2g', 2 * 1024 * 1024 * 1024],
  228. 'unlimited memory' => ['-1', 0],
  229. 'invalid float value' => ['2.5M', 2 * 1024 * 1024],
  230. 'empty value' => ['', 0],
  231. 'invalid ini setting' => ['whatever it takes', 0]
  232. ];
  233. }
  234. /**
  235. * @expectedException \InvalidArgumentException
  236. */
  237. public function testInvalidMaxFramePayloadSizes() {
  238. $messageBuffer = new MessageBuffer(
  239. new CloseFrameChecker(),
  240. function (Message $message) {},
  241. function (Frame $frame) {},
  242. false,
  243. null,
  244. 0,
  245. 0x8000000000000000
  246. );
  247. }
  248. /**
  249. * @expectedException \InvalidArgumentException
  250. */
  251. public function testInvalidMaxMessagePayloadSizes() {
  252. $messageBuffer = new MessageBuffer(
  253. new CloseFrameChecker(),
  254. function (Message $message) {},
  255. function (Frame $frame) {},
  256. false,
  257. null,
  258. 0x8000000000000000,
  259. 0
  260. );
  261. }
  262. /**
  263. * @dataProvider phpConfigurationProvider
  264. *
  265. * @param string $phpConfigurationValue
  266. * @param int $expectedLimit
  267. *
  268. * @runInSeparateProcess
  269. * @requires PHP 7.0
  270. */
  271. public function testIniSizes($phpConfigurationValue, $expectedLimit) {
  272. $value = @ini_set('memory_limit', $phpConfigurationValue);
  273. if ($value === false) {
  274. $this->markTestSkipped("Does not support setting the memory_limit lower than current memory_usage");
  275. }
  276. $messageBuffer = new MessageBuffer(
  277. new CloseFrameChecker(),
  278. function (Message $message) {},
  279. function (Frame $frame) {},
  280. false,
  281. null
  282. );
  283. if ($expectedLimit === -1) {
  284. $expectedLimit = 0;
  285. }
  286. $prop = new \ReflectionProperty($messageBuffer, 'maxMessagePayloadSize');
  287. $prop->setAccessible(true);
  288. $this->assertEquals($expectedLimit / 4, $prop->getValue($messageBuffer));
  289. $prop = new \ReflectionProperty($messageBuffer, 'maxFramePayloadSize');
  290. $prop->setAccessible(true);
  291. $this->assertEquals($expectedLimit / 4, $prop->getValue($messageBuffer));
  292. }
  293. /**
  294. * @runInSeparateProcess
  295. * @requires PHP 7.0
  296. */
  297. public function testInvalidIniSize() {
  298. $value = @ini_set('memory_limit', 'lots of memory');
  299. if ($value === false) {
  300. $this->markTestSkipped("Does not support setting the memory_limit lower than current memory_usage");
  301. }
  302. $messageBuffer = new MessageBuffer(
  303. new CloseFrameChecker(),
  304. function (Message $message) {},
  305. function (Frame $frame) {},
  306. false,
  307. null
  308. );
  309. $prop = new \ReflectionProperty($messageBuffer, 'maxMessagePayloadSize');
  310. $prop->setAccessible(true);
  311. $this->assertEquals(0, $prop->getValue($messageBuffer));
  312. $prop = new \ReflectionProperty($messageBuffer, 'maxFramePayloadSize');
  313. $prop->setAccessible(true);
  314. $this->assertEquals(0, $prop->getValue($messageBuffer));
  315. }
  316. }