SecureIntegrationTest.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <?php
  2. namespace React\Tests\Socket;
  3. use Evenement\EventEmitterInterface;
  4. use React\Promise\Deferred;
  5. use React\Promise\Promise;
  6. use React\Socket\ConnectionInterface;
  7. use React\Socket\SecureConnector;
  8. use React\Socket\SecureServer;
  9. use React\Socket\TcpConnector;
  10. use React\Socket\TcpServer;
  11. class SecureIntegrationTest extends TestCase
  12. {
  13. const TIMEOUT = 2;
  14. private $server;
  15. private $connector;
  16. private $address;
  17. /**
  18. * @before
  19. */
  20. public function setUpConnector()
  21. {
  22. if (defined('HHVM_VERSION')) {
  23. $this->markTestSkipped('Not supported on legacy HHVM');
  24. }
  25. $this->server = new TcpServer(0);
  26. $this->server = new SecureServer($this->server, null, array(
  27. 'local_cert' => __DIR__ . '/../examples/localhost.pem'
  28. ));
  29. $this->address = $this->server->getAddress();
  30. $this->connector = new SecureConnector(new TcpConnector(), null, array('verify_peer' => false));
  31. }
  32. /**
  33. * @after
  34. */
  35. public function tearDownServer()
  36. {
  37. if ($this->server !== null) {
  38. $this->server->close();
  39. $this->server = null;
  40. }
  41. }
  42. public function testConnectToServer()
  43. {
  44. $client = \React\Async\await(\React\Promise\Timer\timeout($this->connector->connect($this->address), self::TIMEOUT));
  45. /* @var $client ConnectionInterface */
  46. $client->close();
  47. // if we reach this, then everything is good
  48. $this->assertNull(null);
  49. }
  50. public function testConnectToServerEmitsConnection()
  51. {
  52. $promiseServer = $this->createPromiseForEvent($this->server, 'connection', $this->expectCallableOnce());
  53. $promiseClient = $this->connector->connect($this->address);
  54. list($_, $client) = \React\Async\await(\React\Promise\Timer\timeout(\React\Promise\all(array($promiseServer, $promiseClient)), self::TIMEOUT));
  55. /* @var $client ConnectionInterface */
  56. $client->close();
  57. }
  58. public function testSendSmallDataToServerReceivesOneChunk()
  59. {
  60. // server expects one connection which emits one data event
  61. $received = new Deferred();
  62. $this->server->on('connection', function (ConnectionInterface $peer) use ($received) {
  63. $peer->on('data', function ($chunk) use ($received) {
  64. $received->resolve($chunk);
  65. });
  66. });
  67. $client = \React\Async\await(\React\Promise\Timer\timeout($this->connector->connect($this->address), self::TIMEOUT));
  68. /* @var $client ConnectionInterface */
  69. $client->write('hello');
  70. // await server to report one "data" event
  71. $data = \React\Async\await(\React\Promise\Timer\timeout($received->promise(), self::TIMEOUT));
  72. $client->close();
  73. $this->assertEquals('hello', $data);
  74. }
  75. public function testSendDataWithEndToServerReceivesAllData()
  76. {
  77. // PHP can report EOF on TLS 1.3 stream before consuming all data, so
  78. // we explicitly use older TLS version instead. Selecting TLS version
  79. // requires PHP 5.6+, so skip legacy versions if TLS 1.3 is supported.
  80. // Continue if TLS 1.3 is not supported anyway.
  81. if ($this->supportsTls13()) {
  82. if (!defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
  83. $this->markTestSkipped('TLS 1.3 supported, but this legacy PHP version does not support explicit choice');
  84. }
  85. $this->connector = new SecureConnector(new TcpConnector(), null, array(
  86. 'verify_peer' => false,
  87. 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
  88. ));
  89. }
  90. $disconnected = new Deferred();
  91. $this->server->on('connection', function (ConnectionInterface $peer) use ($disconnected) {
  92. $received = '';
  93. $peer->on('data', function ($chunk) use (&$received) {
  94. $received .= $chunk;
  95. });
  96. $peer->on('close', function () use (&$received, $disconnected) {
  97. $disconnected->resolve($received);
  98. });
  99. });
  100. $client = \React\Async\await(\React\Promise\Timer\timeout($this->connector->connect($this->address), self::TIMEOUT));
  101. /* @var $client ConnectionInterface */
  102. $data = str_repeat('a', 200000);
  103. $client->end($data);
  104. // await server to report connection "close" event
  105. $received = \React\Async\await(\React\Promise\Timer\timeout($disconnected->promise(), self::TIMEOUT));
  106. $this->assertEquals(strlen($data), strlen($received));
  107. $this->assertEquals($data, $received);
  108. }
  109. public function testSendDataWithoutEndingToServerReceivesAllData()
  110. {
  111. $server = $this->server;
  112. $promise = new Promise(function ($resolve, $reject) use ($server) {
  113. $server->on('connection', function (ConnectionInterface $connection) use ($resolve) {
  114. $received = '';
  115. $connection->on('data', function ($chunk) use (&$received, $resolve) {
  116. $received .= $chunk;
  117. if (strlen($received) >= 200000) {
  118. $resolve($received);
  119. }
  120. });
  121. });
  122. });
  123. $data = str_repeat('d', 200000);
  124. $connecting = $this->connector->connect($this->address);
  125. $connecting->then(function (ConnectionInterface $connection) use ($data) {
  126. $connection->write($data);
  127. });
  128. $received = \React\Async\await(\React\Promise\Timer\timeout($promise, self::TIMEOUT));
  129. $this->assertEquals(strlen($data), strlen($received));
  130. $this->assertEquals($data, $received);
  131. $connecting->then(function (ConnectionInterface $connection) {
  132. $connection->close();
  133. });
  134. }
  135. public function testConnectToServerWhichSendsSmallDataReceivesOneChunk()
  136. {
  137. $this->server->on('connection', function (ConnectionInterface $peer) {
  138. $peer->write('hello');
  139. });
  140. $client = \React\Async\await(\React\Promise\Timer\timeout($this->connector->connect($this->address), self::TIMEOUT));
  141. /* @var $client ConnectionInterface */
  142. // await client to report one "data" event
  143. $receive = $this->createPromiseForEvent($client, 'data', $this->expectCallableOnceWith('hello'));
  144. \React\Async\await(\React\Promise\Timer\timeout($receive, self::TIMEOUT));
  145. $client->close();
  146. }
  147. public function testConnectToServerWhichSendsDataWithEndReceivesAllData()
  148. {
  149. $data = str_repeat('b', 100000);
  150. $this->server->on('connection', function (ConnectionInterface $peer) use ($data) {
  151. $peer->end($data);
  152. });
  153. $client = \React\Async\await(\React\Promise\Timer\timeout($this->connector->connect($this->address), self::TIMEOUT));
  154. /* @var $client ConnectionInterface */
  155. // await data from client until it closes
  156. $received = $this->buffer($client, self::TIMEOUT);
  157. $this->assertEquals($data, $received);
  158. }
  159. public function testConnectToServerWhichSendsDataWithoutEndingReceivesAllData()
  160. {
  161. $data = str_repeat('c', 100000);
  162. $this->server->on('connection', function (ConnectionInterface $peer) use ($data) {
  163. $peer->write($data);
  164. });
  165. $connecting = $this->connector->connect($this->address);
  166. $promise = new Promise(function ($resolve, $reject) use ($connecting) {
  167. $connecting->then(function (ConnectionInterface $connection) use ($resolve) {
  168. $received = 0;
  169. $connection->on('data', function ($chunk) use (&$received, $resolve) {
  170. $received += strlen($chunk);
  171. if ($received >= 100000) {
  172. $resolve($received);
  173. }
  174. });
  175. }, $reject);
  176. });
  177. $received = \React\Async\await(\React\Promise\Timer\timeout($promise, self::TIMEOUT));
  178. $this->assertEquals(strlen($data), $received);
  179. $connecting->then(function (ConnectionInterface $connection) {
  180. $connection->close();
  181. });
  182. }
  183. private function createPromiseForEvent(EventEmitterInterface $emitter, $event, $fn)
  184. {
  185. return new Promise(function ($resolve) use ($emitter, $event, $fn) {
  186. $emitter->on($event, function () use ($resolve, $fn) {
  187. $resolve(call_user_func_array($fn, func_get_args()));
  188. });
  189. });
  190. }
  191. }