From 7280ddcd19555a326e213ef1f69ff8c94a12ce59 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Thu, 9 Mar 2017 18:22:42 -0500 Subject: [PATCH 01/40] Per-message deflate with options work --- src/Handshake/ClientNegotiator.php | 15 +- src/Handshake/PermessageDeflateOptions.php | 182 ++++++++++++++++++++ src/Handshake/RequestVerifier.php | 23 +++ src/Handshake/ResponseVerifier.php | 28 +++- src/Handshake/ServerNegotiator.php | 32 +++- src/Messaging/Frame.php | 17 ++ src/Messaging/Message.php | 12 ++ src/Messaging/MessageBuffer.php | 185 ++++++++++++++++++++- tests/ab/clientRunner.php | 37 +++-- tests/ab/startServer.php | 46 +++-- 10 files changed, 538 insertions(+), 39 deletions(-) create mode 100644 src/Handshake/PermessageDeflateOptions.php diff --git a/src/Handshake/ClientNegotiator.php b/src/Handshake/ClientNegotiator.php index 70856df..95f49ec 100644 --- a/src/Handshake/ClientNegotiator.php +++ b/src/Handshake/ClientNegotiator.php @@ -16,7 +16,7 @@ class ClientNegotiator { */ private $defaultHeader; - function __construct() { + function __construct($enablePerMessageDeflate = false) { $this->verifier = new ResponseVerifier; $this->defaultHeader = new Request('GET', '', [ @@ -25,6 +25,19 @@ class ClientNegotiator { , 'Sec-WebSocket-Version' => $this->getVersion() , 'User-Agent' => "Ratchet" ]); + + if ($enablePerMessageDeflate && (version_compare(PHP_VERSION, '7.0.15', '<') || version_compare(PHP_VERSION, '7.1.0', '='))) { + $enablePerMessageDeflate = false; + } + if ($enablePerMessageDeflate && !function_exists('deflate_add')) { + $enablePerMessageDeflate = false; + } + + if ($enablePerMessageDeflate) { + $this->defaultHeader = $this->defaultHeader->withAddedHeader( + 'Sec-WebSocket-Extensions', + 'permessage-deflate'); + } } public function generateRequest(UriInterface $uri) { diff --git a/src/Handshake/PermessageDeflateOptions.php b/src/Handshake/PermessageDeflateOptions.php new file mode 100644 index 0000000..6b325d8 --- /dev/null +++ b/src/Handshake/PermessageDeflateOptions.php @@ -0,0 +1,182 @@ +getHeader('Sec-Websocket-Extensions'))); + + $configurationRequests = explode(',', $extHeader); + foreach ($configurationRequests as $configurationRequest) { + $parts = explode(';', $configurationRequest); + if (count($parts) == 0) { + continue; + } + + if ($parts[0] !== 'permessage-deflate') { + continue; + } + + array_shift($parts); + $options = new static(); + $options->deflate = true; + foreach ($parts as $part) { + $kv = explode('=', $part); + $key = $kv[0]; + $value = count($kv) > 1 ? $kv[1] : null; + + $validBits = ['8', '9', '10', '11', '12', '13', '14', '15']; + switch ($key) { + case "server_no_context_takeover": + case "client_no_context_takeover": + if ($value !== null) { + throw new \Exception($key . ' must not have a value.'); + } + $value = true; + break; + case "server_max_window_bits": + if (!in_array($value, $validBits)) { + throw new \Exception($key . ' must have a value between 8 and 15.'); + } + break; + case "client_max_window_bits": + if ($value === null) { + $value = '15'; + } + if (!in_array($value, $validBits)) { + throw new \Exception($key . ' must have no value or a value between 8 and 15.'); + } + break; + default: + throw new \Exception('Option "' . $key . '"is not valid for this extension'); + } + + if ($options->$key !== null) { + throw new \Exception('Key specified more than once. Connection must be declined.'); + } + + $options->$key = $value; + } + + if ($options->getClientMaxWindowBits() === null) { + $options->client_max_window_bits = 15; + } + + if ($options->getServerMaxWindowBits() === null) { + $options->server_max_window_bits = 15; + } + + $optionSets[] = $options; + } + + // always put a disabled on the end + $optionSets[] = new static(); + + return $optionSets; + } + + public static function createDisabled() { + return new static(); + } + + public static function validateResponseToRequest(ResponseInterface $response, RequestInterface $request) { + $requestOptions = static::fromRequestOrResponse($request); + $responseOptions = static::fromRequestOrResponse($response); + } + + /** + * @return mixed + */ + public function getServerNoContextTakeover() + { + return $this->server_no_context_takeover; + } + + /** + * @return mixed + */ + public function getClientNoContextTakeover() + { + return false; // always return false unless we want to conserve resources + return $this->client_no_context_takeover; + } + + /** + * @return mixed + */ + public function getServerMaxWindowBits() + { + return $this->server_max_window_bits; + } + + /** + * @return mixed + */ + public function getClientMaxWindowBits() + { + return $this->client_max_window_bits; + } + + /** + * @return bool + */ + public function getDeflate() + { + return $this->deflate; + } + + /** + * @param ResponseInterface $response + * @return ResponseInterface + */ + public function addHeaderToResponse(ResponseInterface $response) + { + if (!$this->deflate) { + return $response; + } + + $header = 'permessage-deflate'; + if ($this->client_max_window_bits != 15) { + $header .= '; client_max_window_bits='. $this->client_max_window_bits; + } + // this would only be needed if you want to save server resources (no buffer needed) + // worse compression +// if ($this->client_no_context_takeover) { +// $header .= '; client_no_context_takeover'; +// } + if ($this->server_max_window_bits != 15) { + $header .= '; server_max_window_bits=' . $this->server_max_window_bits; + } + if ($this->server_no_context_takeover) { + $header .= '; server_no_context_takeover'; + } + + echo $header . "\n"; + + return $response->withAddedHeader('Sec-Websocket-Extensions', $header); + } +} \ No newline at end of file diff --git a/src/Handshake/RequestVerifier.php b/src/Handshake/RequestVerifier.php index 1ace489..dbce9a9 100644 --- a/src/Handshake/RequestVerifier.php +++ b/src/Handshake/RequestVerifier.php @@ -137,4 +137,27 @@ class RequestVerifier { */ public function verifyExtensions($val) { } + + public function getPermessageDeflateOptions(array $requestHeader, array $responseHeader) { + $deflate = true; + if (!isset($requestHeader['Sec-WebSocket-Extensions']) || count(array_filter($requestHeader['Sec-WebSocket-Extensions'], function ($val) { + return 'permessage-deflate' === substr($val, 0, strlen('permessage-deflate')); + })) === 0) { + $deflate = false; + } + + if (!isset($responseHeader['Sec-WebSocket-Extensions']) || count(array_filter($responseHeader['Sec-WebSocket-Extensions'], function ($val) { + return 'permessage-deflate' === substr($val, 0, strlen('permessage-deflate')); + })) === 0) { + $deflate = false; + } + + return [ + 'deflate' => $deflate, + 'no_context_takeover' => false, + 'max_window_bits' => null, + 'request_no_context_takeover' => false, + 'request_max_window_bits' => null + ]; + } } diff --git a/src/Handshake/ResponseVerifier.php b/src/Handshake/ResponseVerifier.php index de03f53..7dc3921 100644 --- a/src/Handshake/ResponseVerifier.php +++ b/src/Handshake/ResponseVerifier.php @@ -18,8 +18,12 @@ class ResponseVerifier { $request->getHeader('Sec-WebSocket-Protocol') , $response->getHeader('Sec-WebSocket-Protocol') ); + $passes += (int)$this->verifyExtensions( + $request->getHeader('Sec-WebSocket-Extensions') + , $response->getHeader('Sec-WebSocket-Extensions') + ); - return (5 === $passes); + return (6 === $passes); } public function verifyStatus($status) { @@ -49,4 +53,26 @@ class ResponseVerifier { public function verifySubProtocol(array $requestHeader, array $responseHeader) { return 0 === count($responseHeader) || count(array_intersect($responseHeader, $requestHeader)) > 0; } + + public function verifyExtensions(array $requestHeader, array $responseHeader) { + if (in_array('permessage-deflate', $responseHeader)) { + return in_array('permessage-deflate', $requestHeader); + } + + return 1; + } + + public function getPermessageDeflateOptions(array $requestHeader, array $responseHeader) { + if (!$this->verifyExtensions($requestHeader, $responseHeader)) { + return false; + } + + return [ + 'deflate' => in_array('permessage-deflate', $responseHeader), + 'no_context_takeover' => false, + 'max_window_bits' => null, + 'request_no_context_takeover' => false, + 'request_max_window_bits' => null + ]; + } } \ No newline at end of file diff --git a/src/Handshake/ServerNegotiator.php b/src/Handshake/ServerNegotiator.php index 5a0073b..8ff15a9 100644 --- a/src/Handshake/ServerNegotiator.php +++ b/src/Handshake/ServerNegotiator.php @@ -17,8 +17,19 @@ class ServerNegotiator implements NegotiatorInterface { private $_strictSubProtocols = false; - public function __construct(RequestVerifier $requestVerifier) { + private $enablePerMessageDeflate = false; + + public function __construct(RequestVerifier $requestVerifier, $enablePerMessageDeflate = false) { $this->verifier = $requestVerifier; + + if ($enablePerMessageDeflate && (version_compare(PHP_VERSION, '7.0.15', '<') || version_compare(PHP_VERSION, '7.1.0', '='))) { + $enablePerMessageDeflate = false; + } + if ($enablePerMessageDeflate && !function_exists('deflate_add')) { + $enablePerMessageDeflate = false; + } + + $this->enablePerMessageDeflate = $enablePerMessageDeflate; } /** @@ -97,12 +108,23 @@ class ServerNegotiator implements NegotiatorInterface { } } - return new Response(101, array_merge($headers, [ + $response = new Response(101, array_merge($headers, [ 'Upgrade' => 'websocket' - , 'Connection' => 'Upgrade' - , 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0]) - , 'X-Powered-By' => 'Ratchet' + , 'Connection' => 'Upgrade' + , 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0]) + , 'X-Powered-By' => 'Ratchet' ])); + + +// $perMessageDeflate = array_filter($request->getHeader('Sec-WebSocket-Extensions'), function ($x) { +// return 'permessage-deflate' === substr($x, 0, strlen('permessage-deflate')); +// }); + $perMessageDeflateRequest = PermessageDeflateOptions::fromRequestOrResponse($request)[0]; + if ($this->enablePerMessageDeflate && $perMessageDeflateRequest->getDeflate()) { + $response = $perMessageDeflateRequest->addHeaderToResponse($response); + } + + return $response; } /** diff --git a/src/Messaging/Frame.php b/src/Messaging/Frame.php index 40f9eb2..f558554 100644 --- a/src/Messaging/Frame.php +++ b/src/Messaging/Frame.php @@ -149,6 +149,23 @@ class Frame implements FrameInterface { return 128 === ($this->firstByte & 128); } + public function setRsv1($value = true) { + if (strlen($this->data) == 0) { + throw new \UnderflowException("Cannot set Rsv1 because there is no data."); + } + + $this->firstByte = + ($this->isFinal() ? 128 : 0) + + $this->getOpcode() + + ($value ? 64 : 0) + + ($this->getRsv2() ? 32 : 0) + + ($this->getRsv3() ? 16 : 0) + ; + + $this->data[0] = chr($this->firstByte); + return $this; + } + /** * @return boolean * @throws \UnderflowException diff --git a/src/Messaging/Message.php b/src/Messaging/Message.php index 4f3b014..06f3949 100644 --- a/src/Messaging/Message.php +++ b/src/Messaging/Message.php @@ -120,4 +120,16 @@ class Message implements \IteratorAggregate, MessageInterface { return Frame::OP_BINARY === $this->_frames->bottom()->getOpcode(); } + + /** + * @return boolean + */ + public function getRsv1() { + if ($this->_frames->isEmpty()) { + return false; + //throw new \UnderflowException('Not enough data has been received to determine if message is binary'); + } + + return $this->_frames->bottom()->getRsv1(); + } } diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index 07ff4f1..2ace8db 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -1,6 +1,8 @@ closeFrameChecker = $frameChecker; $this->checkForMask = (bool)$expectMask; @@ -53,6 +78,14 @@ class MessageBuffer { $this->onMessage = $onMessage; $this->onControl = $onControl ?: function() {}; + + $this->sender = $sender; + + $this->permessageDeflateOptions = $permessageDeflateOptions ? $permessageDeflateOptions : PermessageDeflateOptions::createDisabled(); + + $this->deflate = $this->permessageDeflateOptions->getDeflate(); + + $this->compressedMessage = false; } public function onData($data) { @@ -85,12 +118,19 @@ class MessageBuffer { $opcode = $this->frameBuffer->getOpcode(); if ($opcode > 2) { - $onControl($this->frameBuffer); + $onControl($this->frameBuffer, $this); if (Frame::OP_CLOSE === $opcode) { return ''; } } else { + if ($this->messageBuffer->count() === 0 && $this->frameBuffer->getRsv1()) { + $this->compressedMessage = true; + } + if ($this->compressedMessage) { + $this->frameBuffer = $this->inflateFrame($this->frameBuffer); + } + $this->messageBuffer->addFrame($this->frameBuffer); } @@ -99,12 +139,17 @@ class MessageBuffer { if ($this->messageBuffer->isCoalesced()) { $msgCheck = $this->checkMessage($this->messageBuffer); if (true !== $msgCheck) { - $onControl($this->newCloseFrame($msgCheck, 'Ratchet detected an invalid UTF-8 payload')); + $onControl($this->newCloseFrame($msgCheck, 'Ratchet detected an invalid UTF-8 payload'), $this); } else { - $onMessage($this->messageBuffer); + $onMessage($this->messageBuffer, $this); } $this->messageBuffer = null; + $this->compressedMessage = false; + + if ($this->permessageDeflateOptions->getServerNoContextTakeover()) { + $this->inflator = null; + } } return $overflow; @@ -116,7 +161,7 @@ class MessageBuffer { * @return \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface */ public function frameCheck(FrameInterface $frame) { - if (false !== $frame->getRsv1() || + if ((false !== $frame->getRsv1() && !$this->deflate) || false !== $frame->getRsv2() || false !== $frame->getRsv3() ) { @@ -228,4 +273,134 @@ class MessageBuffer { public function newCloseFrame($code, $reason = '') { return $this->newFrame(pack('n', $code) . $reason, true, Frame::OP_CLOSE); } + + + public function sendFrame(Frame $frame) { + if ($this->deflate && + ($frame->getOpcode() === Frame::OP_TEXT || $frame->getOpcode() === Frame::OP_BINARY)) { + $frame = $this->deflateFrame($frame); + } + + if (!$this->checkForMask) { + $frame->maskPayload(); + } + + $sender = $this->sender; + $sender($frame->getContents()); + } + + public function sendMessage($messagePayload, $final = true, $isBinary = false) { + $opCode = $isBinary ? Frame::OP_BINARY : Frame::OP_TEXT; + if ($this->streamingMessageOpCode === -1) { + $this->streamingMessageOpCode = $opCode; + } + + if ($this->streamingMessageOpCode !== $opCode) { + throw new \Exception('Binary and text message parts cannot be streamed together.'); + } + + $frame = $this->newFrame($messagePayload, $final, $opCode); + + $this->sendFrame($frame); + + if ($final) { + // reset deflator if client doesn't remember contexts + if ($this->permessageDeflateOptions->getClientNoContextTakeover()) { + $this->deflator = null; + } + $this->streamingMessageOpCode = -1; + } + } + + private $inflator; + + private function inflateFrame(Frame $frame) { + if ($this->inflator === null) { +// $this->inflator = inflate_init(ZLIB_ENCODING_RAW); + $this->inflator = inflate_init( + ZLIB_ENCODING_RAW, + [ + 'level' => -1, + 'memory' => 8, + 'window' => $this->permessageDeflateOptions->getClientMaxWindowBits(), + 'strategy' => ZLIB_DEFAULT_STRATEGY + ] + ); + } + + $terminator = ''; + if ($frame->isFinal()) { + $terminator = "\x00\x00\xff\xff"; + } + + gc_collect_cycles(); // memory runs away if we don't collect ?? + + return new Frame( + inflate_add($this->inflator, $frame->getPayload() . $terminator), + $frame->isFinal(), + $frame->getOpcode() + ); + } + private $deflator; + + private function deflateFrame(Frame $frame) { + if ($frame->getRsv1()) { + return $frame; // frame is already deflated + } + + if ($this->deflator === null) { +// $this->deflator = deflate_init( +// ZLIB_ENCODING_RAW +// ); + echo "init with " . $this->permessageDeflateOptions->getServerMaxWindowBits(); + $this->deflator = deflate_init( + ZLIB_ENCODING_RAW, + [ + 'level' => -1, + 'memory' => 8, + 'window' => $this->permessageDeflateOptions->getServerMaxWindowBits(), + 'strategy' => ZLIB_DEFAULT_STRATEGY + ] + ); + } + + // there is an issue in the zlib extension for php where + // deflate_add does not check avail_out to see if the buffer filled + // this only seems to be an issue for payloads between 16 and 64 bytes + // This if statement is a hack fix to break the output up allowing us + // to call deflate_add twice which should clear the buffer issue +// if ($frame->getPayloadLength() >= 16 && $frame->getPayloadLength() <= 64) { +// // try processing in 8 byte chunks +// // https://bugs.php.net/bug.php?id=73373 +// $payload = ""; +// $orig = $frame->getPayload(); +// $partSize = 8; +// while (strlen($orig) > 0) { +// $part = substr($orig, 0, $partSize); +// $orig = substr($orig, strlen($part)); +// $flags = strlen($orig) > 0 ? ZLIB_PARTIAL_FLUSH : ZLIB_SYNC_FLUSH; +// $payload .= deflate_add($this->deflator, $part, $flags); +// } +// } else { + $payload = deflate_add( + $this->deflator, + $frame->getPayload(), + ZLIB_SYNC_FLUSH + ); +// } + + $deflatedFrame = new Frame( + substr($payload, 0, $frame->isFinal() ? -4 : strlen($payload)), + $frame->isFinal(), + $frame->getOpcode() + ); + + if ($frame->isFinal()) { + $deflatedFrame->setRsv1(); + } + + gc_collect_cycles(); // memory runs away if we don't collect ?? + + return $deflatedFrame; + } } diff --git a/tests/ab/clientRunner.php b/tests/ab/clientRunner.php index 0c5578a..bfd90a4 100644 --- a/tests/ab/clientRunner.php +++ b/tests/ab/clientRunner.php @@ -1,5 +1,7 @@ createCached('8.8.8.8', $loop); $factory = new \React\SocketClient\Connector($loop, $dnsResolver); -function echoStreamerFactory($conn) +function echoStreamerFactory($conn, $permessageDeflateOptions = null) { + if ($permessageDeflateOptions === null) { + $permessageDeflateOptions = []; + } + return new \Ratchet\RFC6455\Messaging\MessageBuffer( new \Ratchet\RFC6455\Messaging\CloseFrameChecker, - function (\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($conn) { - /** @var Frame $frame */ - foreach ($msg as $frame) { - $frame->maskPayload(); - } - $conn->write($msg->getContents()); + function (\Ratchet\RFC6455\Messaging\MessageInterface $msg, MessageBuffer $messageBuffer) use ($conn) { + $messageBuffer->sendMessage($msg->getPayload(), true, $msg->isBinary()); }, - function (\Ratchet\RFC6455\Messaging\FrameInterface $frame) use ($conn) { + [$conn, 'write'], + function (\Ratchet\RFC6455\Messaging\FrameInterface $frame, MessageBuffer $messageBuffer) use ($conn) { switch ($frame->getOpcode()) { case Frame::OP_PING: return $conn->write((new Frame($frame->getPayload(), true, Frame::OP_PONG))->maskPayload()->getContents()); @@ -37,7 +40,9 @@ function echoStreamerFactory($conn) break; } }, - false + false, + null, + $permessageDeflateOptions ); } @@ -54,7 +59,7 @@ function getTestCases() { $rawResponse = ""; $response = null; - /** @var \Ratchet\RFC6455\Messaging\Streaming\MessageBuffer $ms */ + /** @var MessageBuffer $ms */ $ms = null; $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) { @@ -76,6 +81,7 @@ function getTestCases() { $deferred->resolve($msg->getPayload()); $stream->close(); }, + function () {}, null, false ); @@ -105,7 +111,7 @@ function runTest($case) $deferred = new Deferred(); $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred, $casePath, $case) { - $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(); + $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(true); $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $casePath)); $rawResponse = ""; @@ -126,7 +132,13 @@ function runTest($case) $stream->end(); $deferred->reject(); } else { - $ms = echoStreamerFactory($stream); + $ms = echoStreamerFactory( + $stream, + (new ResponseVerifier())->getPermessageDeflateOptions( + $cnRequest->getHeader('Sec-WebSocket-Extensions'), + $response->getHeader('Sec-WebSocket-Extensions') + ) + ); } } } @@ -183,6 +195,7 @@ function createReport() { $deferred->resolve($msg->getPayload()); $stream->close(); }, + function () {}, null, false ); diff --git a/tests/ab/startServer.php b/tests/ab/startServer.php index b256ec2..f492e19 100644 --- a/tests/ab/startServer.php +++ b/tests/ab/startServer.php @@ -1,4 +1,8 @@ on('request', function (\React\Http\Request $request, \React\Http\Respo return; } - $parser = new \Ratchet\RFC6455\Messaging\MessageBuffer($closeFrameChecker, function(MessageInterface $message) use ($response) { - $response->write($message->getContents()); - }, function(FrameInterface $frame) use ($response, &$parser) { - switch ($frame->getOpCode()) { - case Frame::OP_CLOSE: - $response->end($frame->getContents()); - break; - case Frame::OP_PING: - $response->write($parser->newFrame($frame->getPayload(), true, Frame::OP_PONG)->getContents()); - break; - } - }, true, function() use ($uException) { - return $uException; - }); + // there is no need to look through the client requests + // we support any valid permessage deflate + $deflateOptions = PermessageDeflateOptions::fromRequestOrResponse($psrRequest)[0]; + + $parser = new \Ratchet\RFC6455\Messaging\MessageBuffer( + $closeFrameChecker, + function (MessageInterface $message, MessageBuffer $messageBuffer) use ($response) { + $messageBuffer->sendMessage($message->getPayload(), true, $message->isBinary()); + }, + [$response, 'write'], + function (FrameInterface $frame) use ($response, &$parser) { + switch ($frame->getOpCode()) { + case Frame::OP_CLOSE: + $response->end($frame->getContents()); + break; + case Frame::OP_PING: + $response->write($parser->newFrame($frame->getPayload(), true, Frame::OP_PONG)->getContents()); + break; + } + }, + true, + function () use ($uException) { + return $uException; + }, + $deflateOptions + ); $request->on('data', [$parser, 'onData']); }); From 8eed9e7db24be7ef710695c7052f00fa1578326c Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Fri, 10 Mar 2017 00:17:23 -0500 Subject: [PATCH 02/40] Server passing tests - client passes most tests - refactoring and API help needed --- src/Handshake/ClientNegotiator.php | 2 +- src/Handshake/PermessageDeflateOptions.php | 11 ++---- src/Messaging/MessageBuffer.php | 45 +++++++++++++++------- tests/ab/clientRunner.php | 13 +++---- tests/ab/startServer.php | 2 - 5 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/Handshake/ClientNegotiator.php b/src/Handshake/ClientNegotiator.php index 95f49ec..b4394df 100644 --- a/src/Handshake/ClientNegotiator.php +++ b/src/Handshake/ClientNegotiator.php @@ -36,7 +36,7 @@ class ClientNegotiator { if ($enablePerMessageDeflate) { $this->defaultHeader = $this->defaultHeader->withAddedHeader( 'Sec-WebSocket-Extensions', - 'permessage-deflate'); + 'permessage-deflate; client_max_window_bits'); } } diff --git a/src/Handshake/PermessageDeflateOptions.php b/src/Handshake/PermessageDeflateOptions.php index 6b325d8..8b91289 100644 --- a/src/Handshake/PermessageDeflateOptions.php +++ b/src/Handshake/PermessageDeflateOptions.php @@ -121,7 +121,6 @@ final class PermessageDeflateOptions */ public function getClientNoContextTakeover() { - return false; // always return false unless we want to conserve resources return $this->client_no_context_takeover; } @@ -163,11 +162,9 @@ final class PermessageDeflateOptions if ($this->client_max_window_bits != 15) { $header .= '; client_max_window_bits='. $this->client_max_window_bits; } - // this would only be needed if you want to save server resources (no buffer needed) - // worse compression -// if ($this->client_no_context_takeover) { -// $header .= '; client_no_context_takeover'; -// } + if ($this->client_no_context_takeover) { + $header .= '; client_no_context_takeover'; + } if ($this->server_max_window_bits != 15) { $header .= '; server_max_window_bits=' . $this->server_max_window_bits; } @@ -175,8 +172,6 @@ final class PermessageDeflateOptions $header .= '; server_no_context_takeover'; } - echo $header . "\n"; - return $response->withAddedHeader('Sec-Websocket-Extensions', $header); } } \ No newline at end of file diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index 2ace8db..b5aba3e 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -124,6 +124,8 @@ class MessageBuffer { return ''; } } else { + //echo "fin: " . json_encode($this->frameBuffer->isFinal()) . ", bCount: " . $this->messageBuffer->count() . ", Rsv1: " . json_encode($this->frameBuffer->getRsv1()) . "\n"; + if ($this->messageBuffer->count() === 0 && $this->frameBuffer->getRsv1()) { $this->compressedMessage = true; } @@ -305,7 +307,7 @@ class MessageBuffer { if ($final) { // reset deflator if client doesn't remember contexts - if ($this->permessageDeflateOptions->getClientNoContextTakeover()) { + if ($this->getDeflateNoContextTakeover()) { $this->deflator = null; } $this->streamingMessageOpCode = -1; @@ -314,17 +316,38 @@ class MessageBuffer { private $inflator; + private function getDeflateNoContextTakeover() { + return $this->checkForMask ? + $this->permessageDeflateOptions->getServerNoContextTakeover() : + $this->permessageDeflateOptions->getClientNoContextTakeover(); + } + + private function getDeflateWindowBits() { + return $this->checkForMask ? $this->permessageDeflateOptions->getServerMaxWindowBits() : $this->permessageDeflateOptions->getClientMaxWindowBits(); + } + + private function getInflateNoContextTakeover() { + return $this->checkForMask ? + $this->permessageDeflateOptions->getClientNoContextTakeover() : + $this->permessageDeflateOptions->getServerNoContextTakeover(); + } + + private function getInflateWindowBits() { + return $this->checkForMask ? $this->permessageDeflateOptions->getClientMaxWindowBits() : $this->permessageDeflateOptions->getServerMaxWindowBits(); + } + private function inflateFrame(Frame $frame) { if ($this->inflator === null) { -// $this->inflator = inflate_init(ZLIB_ENCODING_RAW); + $options = [ + 'level' => -1, + 'memory' => 8, + 'window' => $this->getInflateWindowBits(), + 'strategy' => ZLIB_DEFAULT_STRATEGY + ]; + //echo "inflate_init(RAW, " . json_encode($options) . ")\n"; $this->inflator = inflate_init( ZLIB_ENCODING_RAW, - [ - 'level' => -1, - 'memory' => 8, - 'window' => $this->permessageDeflateOptions->getClientMaxWindowBits(), - 'strategy' => ZLIB_DEFAULT_STRATEGY - ] + $options ); } @@ -349,16 +372,12 @@ class MessageBuffer { } if ($this->deflator === null) { -// $this->deflator = deflate_init( -// ZLIB_ENCODING_RAW -// ); - echo "init with " . $this->permessageDeflateOptions->getServerMaxWindowBits(); $this->deflator = deflate_init( ZLIB_ENCODING_RAW, [ 'level' => -1, 'memory' => 8, - 'window' => $this->permessageDeflateOptions->getServerMaxWindowBits(), + 'window' => $this->getDeflateWindowBits(), 'strategy' => ZLIB_DEFAULT_STRATEGY ] ); diff --git a/tests/ab/clientRunner.php b/tests/ab/clientRunner.php index bfd90a4..2cd963e 100644 --- a/tests/ab/clientRunner.php +++ b/tests/ab/clientRunner.php @@ -1,5 +1,7 @@ create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred, $casePath, $case) { $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(true); + /** @var RequestInterface $cnRequest */ $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $casePath)); $rawResponse = ""; @@ -129,15 +128,13 @@ function runTest($case) $response = \GuzzleHttp\Psr7\parse_response($rawResponse); if (!$cn->validateResponse($cnRequest, $response)) { + echo "Invalid response.\n"; $stream->end(); $deferred->reject(); } else { $ms = echoStreamerFactory( $stream, - (new ResponseVerifier())->getPermessageDeflateOptions( - $cnRequest->getHeader('Sec-WebSocket-Extensions'), - $response->getHeader('Sec-WebSocket-Extensions') - ) + PermessageDeflateOptions::fromRequestOrResponse($response)[0] ); } } diff --git a/tests/ab/startServer.php b/tests/ab/startServer.php index f492e19..515065a 100644 --- a/tests/ab/startServer.php +++ b/tests/ab/startServer.php @@ -1,7 +1,5 @@ Date: Sat, 11 Mar 2017 02:16:23 -0500 Subject: [PATCH 03/40] Client passes all per message deflate autobahn tests --- src/Handshake/ClientNegotiator.php | 21 ++-- ...validPermessageDeflateOptionsException.php | 7 ++ src/Handshake/PermessageDeflateOptions.php | 107 +++++++++++++++--- src/Handshake/ResponseVerifier.php | 18 +-- src/Handshake/ServerNegotiator.php | 7 +- src/Messaging/MessageBuffer.php | 2 +- tests/ab/clientRunner.php | 13 ++- 7 files changed, 127 insertions(+), 48 deletions(-) create mode 100644 src/Handshake/InvalidPermessageDeflateOptionsException.php diff --git a/src/Handshake/ClientNegotiator.php b/src/Handshake/ClientNegotiator.php index b4394df..a514eb7 100644 --- a/src/Handshake/ClientNegotiator.php +++ b/src/Handshake/ClientNegotiator.php @@ -16,7 +16,7 @@ class ClientNegotiator { */ private $defaultHeader; - function __construct($enablePerMessageDeflate = false) { + function __construct(PermessageDeflateOptions $perMessageDeflateOptions = null) { $this->verifier = new ResponseVerifier; $this->defaultHeader = new Request('GET', '', [ @@ -26,18 +26,19 @@ class ClientNegotiator { , 'User-Agent' => "Ratchet" ]); - if ($enablePerMessageDeflate && (version_compare(PHP_VERSION, '7.0.15', '<') || version_compare(PHP_VERSION, '7.1.0', '='))) { - $enablePerMessageDeflate = false; + // https://bugs.php.net/bug.php?id=73373 + if ($perMessageDeflateOptions === null) { + $perMessageDeflateOptions = PermessageDeflateOptions::createDisabled(); } - if ($enablePerMessageDeflate && !function_exists('deflate_add')) { - $enablePerMessageDeflate = false; + if ( + version_compare(PHP_VERSION, '7.0.15', '<') + || version_compare(PHP_VERSION, '7.1.0', '=') + || !function_exists('deflate_add') + ) { + $perMessageDeflateOptions = PermessageDeflateOptions::createDisabled(); } - if ($enablePerMessageDeflate) { - $this->defaultHeader = $this->defaultHeader->withAddedHeader( - 'Sec-WebSocket-Extensions', - 'permessage-deflate; client_max_window_bits'); - } + $this->defaultHeader = $perMessageDeflateOptions->addHeaderToRequest($this->defaultHeader); } public function generateRequest(UriInterface $uri) { diff --git a/src/Handshake/InvalidPermessageDeflateOptionsException.php b/src/Handshake/InvalidPermessageDeflateOptionsException.php new file mode 100644 index 0000000..191e7a5 --- /dev/null +++ b/src/Handshake/InvalidPermessageDeflateOptionsException.php @@ -0,0 +1,7 @@ +deflate = true; + $new->client_max_window_bits = self::MAX_WINDOW_BITS; + $new->client_no_context_takeover = false; + $new->server_max_window_bits = self::MAX_WINDOW_BITS; + $new->server_no_context_takeover = false; + return $new; + } + + public static function createDisabled() { + return new static(); + } + + public function withClientNoContextTakeover() { + $new = clone $this; + $new->client_no_context_takeover = true; + } + + public function withoutClientNoContextTakeover() { + $new = clone $this; + $new->client_no_context_takeover = false; + } + + public function withServerNoContextTakeover() { + $new = clone $this; + $new->server_no_context_takeover = true; + } + + public function withoutServerNoContextTakeover() { + $new = clone $this; + $new->server_no_context_takeover = false; + } + + public function withServerMaxWindowBits($bits = self::MAX_WINDOW_BITS) { + if (!in_array($bits, self::VALID_BITS)) { + throw new \Exception('server_max_window_bits must have a value between 8 and 15.'); + } + $new = clone $this; + $new->server_max_window_bits = $bits; + } + + public function withClientMaxWindowBits($bits = self::MAX_WINDOW_BITS) { + if (!in_array($bits, self::VALID_BITS)) { + throw new \Exception('client_max_window_bits must have a value between 8 and 15.'); + } + $new = clone $this; + $new->client_max_window_bits = $bits; + } + /** * https://tools.ietf.org/html/rfc6455#section-9.1 * https://tools.ietf.org/html/rfc7692#section-7 @@ -49,34 +102,33 @@ final class PermessageDeflateOptions $key = $kv[0]; $value = count($kv) > 1 ? $kv[1] : null; - $validBits = ['8', '9', '10', '11', '12', '13', '14', '15']; switch ($key) { case "server_no_context_takeover": case "client_no_context_takeover": if ($value !== null) { - throw new \Exception($key . ' must not have a value.'); + throw new InvalidPermessageDeflateOptionsException($key . ' must not have a value.'); } $value = true; break; case "server_max_window_bits": - if (!in_array($value, $validBits)) { - throw new \Exception($key . ' must have a value between 8 and 15.'); + if (!in_array($value, self::VALID_BITS)) { + throw new InvalidPermessageDeflateOptionsException($key . ' must have a value between 8 and 15.'); } break; case "client_max_window_bits": if ($value === null) { $value = '15'; } - if (!in_array($value, $validBits)) { - throw new \Exception($key . ' must have no value or a value between 8 and 15.'); + if (!in_array($value, self::VALID_BITS)) { + throw new InvalidPermessageDeflateOptionsException($key . ' must have no value or a value between 8 and 15.'); } break; default: - throw new \Exception('Option "' . $key . '"is not valid for this extension'); + throw new InvalidPermessageDeflateOptionsException('Option "' . $key . '"is not valid for permessage deflate'); } if ($options->$key !== null) { - throw new \Exception('Key specified more than once. Connection must be declined.'); + throw new InvalidPermessageDeflateOptionsException($key . ' specified more than once. Connection must be declined.'); } $options->$key = $value; @@ -99,14 +151,10 @@ final class PermessageDeflateOptions return $optionSets; } - public static function createDisabled() { - return new static(); - } - - public static function validateResponseToRequest(ResponseInterface $response, RequestInterface $request) { - $requestOptions = static::fromRequestOrResponse($request); - $responseOptions = static::fromRequestOrResponse($response); - } +// public static function validateResponseToRequest(ResponseInterface $response, RequestInterface $request) { +// $requestOptions = static::fromRequestOrResponse($request); +// $responseOptions = static::fromRequestOrResponse($response); +// } /** * @return mixed @@ -174,4 +222,27 @@ final class PermessageDeflateOptions return $response->withAddedHeader('Sec-Websocket-Extensions', $header); } -} \ No newline at end of file + + public function addHeaderToRequest(RequestInterface $request) { + if (!$this->deflate) { + return $request; + } + + $header = 'permessage-deflate'; + if ($this->server_no_context_takeover) { + $header .= '; server_no_context_takeover'; + } + if ($this->client_no_context_takeover) { + $header .= '; client_no_context_takeover'; + } + if ($this->server_max_window_bits != 15) { + $header .= '; server_max_window_bits=' . $this->server_max_window_bits; + } + $header .= '; client_max_window_bits'; + if ($this->client_max_window_bits != 15) { + $header .= '='. $this->client_max_window_bits; + } + + return $request->withAddedHeader('Sec-Websocket-Extensions', $header); + } +} diff --git a/src/Handshake/ResponseVerifier.php b/src/Handshake/ResponseVerifier.php index 7dc3921..38904e8 100644 --- a/src/Handshake/ResponseVerifier.php +++ b/src/Handshake/ResponseVerifier.php @@ -56,23 +56,9 @@ class ResponseVerifier { public function verifyExtensions(array $requestHeader, array $responseHeader) { if (in_array('permessage-deflate', $responseHeader)) { - return in_array('permessage-deflate', $requestHeader); + return strpos(implode(',', $requestHeader), 'permessage-deflate') !== false ? 1 : 0; } return 1; } - - public function getPermessageDeflateOptions(array $requestHeader, array $responseHeader) { - if (!$this->verifyExtensions($requestHeader, $responseHeader)) { - return false; - } - - return [ - 'deflate' => in_array('permessage-deflate', $responseHeader), - 'no_context_takeover' => false, - 'max_window_bits' => null, - 'request_no_context_takeover' => false, - 'request_max_window_bits' => null - ]; - } -} \ No newline at end of file +} diff --git a/src/Handshake/ServerNegotiator.php b/src/Handshake/ServerNegotiator.php index 8ff15a9..9e72776 100644 --- a/src/Handshake/ServerNegotiator.php +++ b/src/Handshake/ServerNegotiator.php @@ -119,7 +119,12 @@ class ServerNegotiator implements NegotiatorInterface { // $perMessageDeflate = array_filter($request->getHeader('Sec-WebSocket-Extensions'), function ($x) { // return 'permessage-deflate' === substr($x, 0, strlen('permessage-deflate')); // }); - $perMessageDeflateRequest = PermessageDeflateOptions::fromRequestOrResponse($request)[0]; + try { + $perMessageDeflateRequest = PermessageDeflateOptions::fromRequestOrResponse($request)[0]; + } catch (InvalidPermessageDeflateOptionsException $e) { + return new Response(400, [], null, '1.1', $e->getMessage()); + } + if ($this->enablePerMessageDeflate && $perMessageDeflateRequest->getDeflate()) { $response = $perMessageDeflateRequest->addHeaderToResponse($response); } diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index b5aba3e..dbf704c 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -81,7 +81,7 @@ class MessageBuffer { $this->sender = $sender; - $this->permessageDeflateOptions = $permessageDeflateOptions ? $permessageDeflateOptions : PermessageDeflateOptions::createDisabled(); + $this->permessageDeflateOptions = $permessageDeflateOptions ?: PermessageDeflateOptions::createDisabled(); $this->deflate = $this->permessageDeflateOptions->getDeflate(); diff --git a/tests/ab/clientRunner.php b/tests/ab/clientRunner.php index 2cd963e..b51c4c0 100644 --- a/tests/ab/clientRunner.php +++ b/tests/ab/clientRunner.php @@ -1,6 +1,7 @@ create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred, $casePath, $case) { - $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(true); + $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(PermessageDeflateOptions::createDefault()); /** @var RequestInterface $cnRequest */ $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $casePath)); @@ -132,9 +135,15 @@ function runTest($case) $stream->end(); $deferred->reject(); } else { + try { + $permessageDeflateOptions = PermessageDeflateOptions::fromRequestOrResponse($response)[0]; + } catch (InvalidPermessageDeflateOptionsException $e) { + $stream->end(); + } + $ms = echoStreamerFactory( $stream, - PermessageDeflateOptions::fromRequestOrResponse($response)[0] + $permessageDeflateOptions ); } } From bc16757a613f69109d22ac014d7af722e2786590 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Tue, 28 Mar 2017 23:25:19 -0400 Subject: [PATCH 04/40] Fixup version check for permessage-deflate --- src/Handshake/ClientNegotiator.php | 14 ++++++++------ src/Handshake/ServerNegotiator.php | 11 ++++++----- src/Messaging/MessageBuffer.php | 18 ++++++------------ tests/ab/fuzzingclient.json | 2 +- tests/ab/fuzzingserver.json | 2 +- 5 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/Handshake/ClientNegotiator.php b/src/Handshake/ClientNegotiator.php index a514eb7..52bcebc 100644 --- a/src/Handshake/ClientNegotiator.php +++ b/src/Handshake/ClientNegotiator.php @@ -26,15 +26,17 @@ class ClientNegotiator { , 'User-Agent' => "Ratchet" ]); - // https://bugs.php.net/bug.php?id=73373 if ($perMessageDeflateOptions === null) { $perMessageDeflateOptions = PermessageDeflateOptions::createDisabled(); } - if ( - version_compare(PHP_VERSION, '7.0.15', '<') - || version_compare(PHP_VERSION, '7.1.0', '=') - || !function_exists('deflate_add') - ) { + + // https://bugs.php.net/bug.php?id=73373 + // https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18 + $supported = version_compare(PHP_VERSION, '7.0.18', '>=') && !version_compare(PHP_VERSION, '7.1.4', '<'); + if (!$supported) { + if ($perMessageDeflateOptions->getDeflate()) { + trigger_error('permessage-deflate is being disabled because it is not support by your PHP version.', E_USER_NOTICE); + } $perMessageDeflateOptions = PermessageDeflateOptions::createDisabled(); } diff --git a/src/Handshake/ServerNegotiator.php b/src/Handshake/ServerNegotiator.php index 9e72776..3ee7e86 100644 --- a/src/Handshake/ServerNegotiator.php +++ b/src/Handshake/ServerNegotiator.php @@ -22,10 +22,15 @@ class ServerNegotiator implements NegotiatorInterface { public function __construct(RequestVerifier $requestVerifier, $enablePerMessageDeflate = false) { $this->verifier = $requestVerifier; - if ($enablePerMessageDeflate && (version_compare(PHP_VERSION, '7.0.15', '<') || version_compare(PHP_VERSION, '7.1.0', '='))) { + // https://bugs.php.net/bug.php?id=73373 + // https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18 + $supported = version_compare(PHP_VERSION, '7.0.18', '>=') && !version_compare(PHP_VERSION, '7.1.4', '<'); + if ($enablePerMessageDeflate && !$supported) { + trigger_error('permessage-deflate is being disabled because it is not support by your PHP version.', E_USER_NOTICE); $enablePerMessageDeflate = false; } if ($enablePerMessageDeflate && !function_exists('deflate_add')) { + trigger_error('permessage-deflate is being disabled because you do not have the zlib extension.', E_USER_NOTICE); $enablePerMessageDeflate = false; } @@ -115,10 +120,6 @@ class ServerNegotiator implements NegotiatorInterface { , 'X-Powered-By' => 'Ratchet' ])); - -// $perMessageDeflate = array_filter($request->getHeader('Sec-WebSocket-Extensions'), function ($x) { -// return 'permessage-deflate' === substr($x, 0, strlen('permessage-deflate')); -// }); try { $perMessageDeflateRequest = PermessageDeflateOptions::fromRequestOrResponse($request)[0]; } catch (InvalidPermessageDeflateOptionsException $e) { diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index dbf704c..63e22ff 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -124,8 +124,6 @@ class MessageBuffer { return ''; } } else { - //echo "fin: " . json_encode($this->frameBuffer->isFinal()) . ", bCount: " . $this->messageBuffer->count() . ", Rsv1: " . json_encode($this->frameBuffer->getRsv1()) . "\n"; - if ($this->messageBuffer->count() === 0 && $this->frameBuffer->getRsv1()) { $this->compressedMessage = true; } @@ -338,16 +336,14 @@ class MessageBuffer { private function inflateFrame(Frame $frame) { if ($this->inflator === null) { - $options = [ - 'level' => -1, - 'memory' => 8, - 'window' => $this->getInflateWindowBits(), - 'strategy' => ZLIB_DEFAULT_STRATEGY - ]; - //echo "inflate_init(RAW, " . json_encode($options) . ")\n"; $this->inflator = inflate_init( ZLIB_ENCODING_RAW, - $options + [ + 'level' => -1, + 'memory' => 8, + 'window' => $this->getInflateWindowBits(), + 'strategy' => ZLIB_DEFAULT_STRATEGY + ] ); } @@ -418,8 +414,6 @@ class MessageBuffer { $deflatedFrame->setRsv1(); } - gc_collect_cycles(); // memory runs away if we don't collect ?? - return $deflatedFrame; } } diff --git a/tests/ab/fuzzingclient.json b/tests/ab/fuzzingclient.json index d2fd0d0..0e9fed0 100644 --- a/tests/ab/fuzzingclient.json +++ b/tests/ab/fuzzingclient.json @@ -9,6 +9,6 @@ , "options": {"version": 18} }] , "cases": ["*"] - , "exclude-cases": ["6.4.*", "12.*","13.*"] + , "exclude-cases": [] , "exclude-agent-cases": {} } diff --git a/tests/ab/fuzzingserver.json b/tests/ab/fuzzingserver.json index 0422560..06fa055 100644 --- a/tests/ab/fuzzingserver.json +++ b/tests/ab/fuzzingserver.json @@ -5,6 +5,6 @@ } , "outdir": "./reports/clients" , "cases": ["*"] - , "exclude-cases": ["6.4.*", "12.*", "13.*"] + , "exclude-cases": [] , "exclude-agent-cases": {} } From d7e3c1bc64afa023f223fcea3665d7a58eb23655 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Tue, 28 Mar 2017 23:27:26 -0400 Subject: [PATCH 05/40] Fix MessageBufferTest to match API change --- tests/unit/Messaging/MessageBufferTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/Messaging/MessageBufferTest.php b/tests/unit/Messaging/MessageBufferTest.php index c33ff0c..3de8561 100644 --- a/tests/unit/Messaging/MessageBufferTest.php +++ b/tests/unit/Messaging/MessageBufferTest.php @@ -28,6 +28,7 @@ class MessageBufferTest extends \PHPUnit_Framework_TestCase $messageCount++; $this->assertEquals('a', $message->getPayload()); }, + function () {}, null, false ); From bc7bf9f3021de7958c4f887b8498d484ddd833eb Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Wed, 29 Mar 2017 10:37:50 -0400 Subject: [PATCH 06/40] Drop support for php 5 and HHVM in travis --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 11d51b4..1d91756 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,8 @@ language: php php: - - 5.4 - - 5.5 - - 5.6 - 7 - - hhvm + - 7.1 before_install: - export PATH=$HOME/.local/bin:$PATH From 34bf40a7758de6077e240b9a346881bd3cdd4983 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Fri, 21 Apr 2017 17:57:03 -0400 Subject: [PATCH 07/40] Revert "Drop support for php 5 and HHVM in travis" This reverts commit a1f7487ff81bd914c69c425984803a9f2eaf51a1. --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1d91756..11d51b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,11 @@ language: php php: + - 5.4 + - 5.5 + - 5.6 - 7 - - 7.1 + - hhvm before_install: - export PATH=$HOME/.local/bin:$PATH From 5f7f53e7eb617db6d8eedc153c96d8a98c669d1c Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Fri, 21 Apr 2017 22:24:40 -0400 Subject: [PATCH 08/40] Work on php version support and testing --- .travis.yml | 2 ++ src/Handshake/ClientNegotiator.php | 2 +- src/Handshake/PermessageDeflateOptions.php | 26 +++++++++++++---- src/Handshake/ServerNegotiator.php | 2 +- tests/ab/clientRunner.php | 6 ++-- tests/ab/fuzzingclient_skip_deflate.json | 14 +++++++++ tests/ab/fuzzingserver_skip_deflate.json | 10 +++++++ tests/ab/run_ab_tests.sh | 16 ++++++++-- .../PermessageDeflateOptionsTest.php | 29 +++++++++++++++++++ 9 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 tests/ab/fuzzingclient_skip_deflate.json create mode 100644 tests/ab/fuzzingserver_skip_deflate.json create mode 100644 tests/unit/Handshake/PermessageDeflateOptionsTest.php diff --git a/.travis.yml b/.travis.yml index 11d51b4..f51c586 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,9 @@ php: - 5.5 - 5.6 - 7 + - 7.1 - hhvm + - nightly before_install: - export PATH=$HOME/.local/bin:$PATH diff --git a/src/Handshake/ClientNegotiator.php b/src/Handshake/ClientNegotiator.php index 52bcebc..4acf5e4 100644 --- a/src/Handshake/ClientNegotiator.php +++ b/src/Handshake/ClientNegotiator.php @@ -32,7 +32,7 @@ class ClientNegotiator { // https://bugs.php.net/bug.php?id=73373 // https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18 - $supported = version_compare(PHP_VERSION, '7.0.18', '>=') && !version_compare(PHP_VERSION, '7.1.4', '<'); + $supported = PermessageDeflateOptions::permessageDeflateSupported(); if (!$supported) { if ($perMessageDeflateOptions->getDeflate()) { trigger_error('permessage-deflate is being disabled because it is not support by your PHP version.', E_USER_NOTICE); diff --git a/src/Handshake/PermessageDeflateOptions.php b/src/Handshake/PermessageDeflateOptions.php index 6d3ff3f..ff983c1 100644 --- a/src/Handshake/PermessageDeflateOptions.php +++ b/src/Handshake/PermessageDeflateOptions.php @@ -9,7 +9,8 @@ use Psr\Http\Message\ResponseInterface; final class PermessageDeflateOptions { const MAX_WINDOW_BITS = 15; - const VALID_BITS = ['8', '9', '10', '11', '12', '13', '14', '15']; + /* this is a private instead of const for 5.4 compatibility */ + private static $VALID_BITS = ['8', '9', '10', '11', '12', '13', '14', '15']; private $deflate = false; @@ -55,7 +56,7 @@ final class PermessageDeflateOptions } public function withServerMaxWindowBits($bits = self::MAX_WINDOW_BITS) { - if (!in_array($bits, self::VALID_BITS)) { + if (!in_array($bits, self::$VALID_BITS)) { throw new \Exception('server_max_window_bits must have a value between 8 and 15.'); } $new = clone $this; @@ -63,7 +64,7 @@ final class PermessageDeflateOptions } public function withClientMaxWindowBits($bits = self::MAX_WINDOW_BITS) { - if (!in_array($bits, self::VALID_BITS)) { + if (!in_array($bits, self::$VALID_BITS)) { throw new \Exception('client_max_window_bits must have a value between 8 and 15.'); } $new = clone $this; @@ -111,7 +112,7 @@ final class PermessageDeflateOptions $value = true; break; case "server_max_window_bits": - if (!in_array($value, self::VALID_BITS)) { + if (!in_array($value, self::$VALID_BITS)) { throw new InvalidPermessageDeflateOptionsException($key . ' must have a value between 8 and 15.'); } break; @@ -119,7 +120,7 @@ final class PermessageDeflateOptions if ($value === null) { $value = '15'; } - if (!in_array($value, self::VALID_BITS)) { + if (!in_array($value, self::$VALID_BITS)) { throw new InvalidPermessageDeflateOptionsException($key . ' must have no value or a value between 8 and 15.'); } break; @@ -245,4 +246,19 @@ final class PermessageDeflateOptions return $request->withAddedHeader('Sec-Websocket-Extensions', $header); } + + public static function permessageDeflateSupported($version = PHP_VERSION) { + if (!function_exists('deflate_init')) { + return false; + } + if (version_compare($version, '7.1.3', '>')) { + return true; + } + if (version_compare($version, '7.0.18', '>=') + && version_compare($version, '7.1.0', '<')) { + return true; + } + + return false; + } } diff --git a/src/Handshake/ServerNegotiator.php b/src/Handshake/ServerNegotiator.php index 3ee7e86..51752e3 100644 --- a/src/Handshake/ServerNegotiator.php +++ b/src/Handshake/ServerNegotiator.php @@ -24,7 +24,7 @@ class ServerNegotiator implements NegotiatorInterface { // https://bugs.php.net/bug.php?id=73373 // https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18 - $supported = version_compare(PHP_VERSION, '7.0.18', '>=') && !version_compare(PHP_VERSION, '7.1.4', '<'); + $supported = PermessageDeflateOptions::permessageDeflateSupported(); if ($enablePerMessageDeflate && !$supported) { trigger_error('permessage-deflate is being disabled because it is not support by your PHP version.', E_USER_NOTICE); $enablePerMessageDeflate = false; diff --git a/tests/ab/clientRunner.php b/tests/ab/clientRunner.php index b51c4c0..60f4479 100644 --- a/tests/ab/clientRunner.php +++ b/tests/ab/clientRunner.php @@ -102,17 +102,19 @@ function getTestCases() { return $deferred->promise(); } +$cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(PermessageDeflateOptions::createDefault()); + function runTest($case) { global $factory; global $testServer; + global $cn; $casePath = "/runCase?case={$case}&agent=" . AGENT; $deferred = new Deferred(); - $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred, $casePath, $case) { - $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(PermessageDeflateOptions::createDefault()); + $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($cn, $deferred, $casePath, $case) { /** @var RequestInterface $cnRequest */ $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $casePath)); diff --git a/tests/ab/fuzzingclient_skip_deflate.json b/tests/ab/fuzzingclient_skip_deflate.json new file mode 100644 index 0000000..88732c9 --- /dev/null +++ b/tests/ab/fuzzingclient_skip_deflate.json @@ -0,0 +1,14 @@ +{ + "options": { + "failByDrop": false + } + , "outdir": "./reports/servers" + , "servers": [{ + "agent": "RatchetRFC/0.1.0" + , "url": "ws://localhost:9001" + , "options": {"version": 18} + }] + , "cases": ["*"] + , "exclude-cases": ["12.*", "13.*"] + , "exclude-agent-cases": {} +} diff --git a/tests/ab/fuzzingserver_skip_deflate.json b/tests/ab/fuzzingserver_skip_deflate.json new file mode 100644 index 0000000..3b90fc3 --- /dev/null +++ b/tests/ab/fuzzingserver_skip_deflate.json @@ -0,0 +1,10 @@ +{ + "url": "ws://127.0.0.1:9001" + , "options": { + "failByDrop": false + } + , "outdir": "./reports/clients" + , "cases": ["*"] + , "exclude-cases": ["12.*", "13.*"] + , "exclude-agent-cases": {} +} diff --git a/tests/ab/run_ab_tests.sh b/tests/ab/run_ab_tests.sh index 8fa9ced..16dcfd7 100644 --- a/tests/ab/run_ab_tests.sh +++ b/tests/ab/run_ab_tests.sh @@ -1,11 +1,23 @@ +set -x cd tests/ab -wstest -m fuzzingserver -s fuzzingserver.json & +SKIP_DEFLATE= +if [ "$TRAVIS" = "true" ]; then +if [ $(phpenv version-name) = "hhvm" -o $(phpenv version-name) = "5.4" -o $(phpenv version-name) = "5.5" -o $(phpenv version-name) = "5.6" ]; then + echo "Skipping deflate autobahn tests for $(phpenv version-name)" + SKIP_DEFLATE=_skip_deflate +fi +fi + +wstest -m fuzzingserver -s fuzzingserver$SKIP_DEFLATE.json & sleep 5 php clientRunner.php sleep 2 php startServer.php & +PHP_SERVER_PID=$! sleep 3 -wstest -m fuzzingclient -s fuzzingclient.json +wstest -m fuzzingclient -s fuzzingclient$SKIP_DEFLATE.json + +kill $PHP_SERVER_PID diff --git a/tests/unit/Handshake/PermessageDeflateOptionsTest.php b/tests/unit/Handshake/PermessageDeflateOptionsTest.php new file mode 100644 index 0000000..4b5fbe1 --- /dev/null +++ b/tests/unit/Handshake/PermessageDeflateOptionsTest.php @@ -0,0 +1,29 @@ +assertEquals($supported, PermessageDeflateOptions::permessageDeflateSupported($version)); + } +} \ No newline at end of file From c943ee17716ddc26745d0084e4b2e9ca10ed6f9d Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Fri, 21 Apr 2017 23:43:28 -0400 Subject: [PATCH 09/40] Reduce the number of tests so travis doesn't time out --- tests/ab/fuzzingclient.json | 25 ++++++++++++++++++++++++- tests/ab/fuzzingserver.json | 25 ++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/tests/ab/fuzzingclient.json b/tests/ab/fuzzingclient.json index 0e9fed0..c43d414 100644 --- a/tests/ab/fuzzingclient.json +++ b/tests/ab/fuzzingclient.json @@ -8,7 +8,30 @@ , "url": "ws://localhost:9001" , "options": {"version": 18} }] - , "cases": ["*"] + , "cases": [ + "1.*", + "2.*", + "3.*", + "4.*", + "5.*", + "6.*", + "7.*", + "8.*", + "9.*", + "10.*", + "12.1.1", + "12.2.1", + "12.3.1", + "12.4.1", + "12.5.1", + "13.1.1", + "13.2.1", + "13.3.1", + "13.4.1", + "13.5.1", + "13.6.1", + "13.7.1" + ] , "exclude-cases": [] , "exclude-agent-cases": {} } diff --git a/tests/ab/fuzzingserver.json b/tests/ab/fuzzingserver.json index 06fa055..9f921a4 100644 --- a/tests/ab/fuzzingserver.json +++ b/tests/ab/fuzzingserver.json @@ -4,7 +4,30 @@ "failByDrop": false } , "outdir": "./reports/clients" - , "cases": ["*"] + , "cases": [ + "1.*", + "2.*", + "3.*", + "4.*", + "5.*", + "6.*", + "7.*", + "8.*", + "9.*", + "10.*", + "12.1.1", + "12.2.1", + "12.3.1", + "12.4.1", + "12.5.1", + "13.1.1", + "13.2.1", + "13.3.1", + "13.4.1", + "13.5.1", + "13.6.1", + "13.7.1" + ] , "exclude-cases": [] , "exclude-agent-cases": {} } From 61ffa6aa2aa85250482a1ed2fb7284f1c48d80aa Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Tue, 23 May 2017 17:25:21 -0400 Subject: [PATCH 10/40] Move sender parameter to maintain BC with previous version --- src/Messaging/MessageBuffer.php | 10 +++++++++- tests/ab/clientRunner.php | 12 +++++++----- tests/ab/startServer.php | 2 +- tests/unit/Messaging/MessageBufferTest.php | 1 - 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index 63e22ff..e130a78 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -63,10 +63,10 @@ class MessageBuffer { function __construct( CloseFrameChecker $frameChecker, callable $onMessage, - callable $sender, callable $onControl = null, $expectMask = true, $exceptionFactory = null, + callable $sender = null, PermessageDeflateOptions $permessageDeflateOptions = null ) { $this->closeFrameChecker = $frameChecker; @@ -85,6 +85,10 @@ class MessageBuffer { $this->deflate = $this->permessageDeflateOptions->getDeflate(); + if ($this->deflate && !is_callable($this->sender)) { + throw new \InvalidArgumentException('sender must be set when deflate is enabled'); + } + $this->compressedMessage = false; } @@ -276,6 +280,10 @@ class MessageBuffer { public function sendFrame(Frame $frame) { + if ($this->sender === null) { + throw new \Exception('To send frames using the MessageBuffer, sender must be set.'); + } + if ($this->deflate && ($frame->getOpcode() === Frame::OP_TEXT || $frame->getOpcode() === Frame::OP_BINARY)) { $frame = $this->deflateFrame($frame); diff --git a/tests/ab/clientRunner.php b/tests/ab/clientRunner.php index 60f4479..2094099 100644 --- a/tests/ab/clientRunner.php +++ b/tests/ab/clientRunner.php @@ -30,7 +30,6 @@ function echoStreamerFactory($conn, $permessageDeflateOptions = null) function (\Ratchet\RFC6455\Messaging\MessageInterface $msg, MessageBuffer $messageBuffer) use ($conn) { $messageBuffer->sendMessage($msg->getPayload(), true, $msg->isBinary()); }, - [$conn, 'write'], function (\Ratchet\RFC6455\Messaging\FrameInterface $frame, MessageBuffer $messageBuffer) use ($conn) { switch ($frame->getOpcode()) { case Frame::OP_PING: @@ -43,6 +42,7 @@ function echoStreamerFactory($conn, $permessageDeflateOptions = null) }, false, null, + [$conn, 'write'], $permessageDeflateOptions ); } @@ -82,9 +82,10 @@ function getTestCases() { $deferred->resolve($msg->getPayload()); $stream->close(); }, - function () {}, null, - false + false, + null, + function () {} ); } } @@ -203,9 +204,10 @@ function createReport() { $deferred->resolve($msg->getPayload()); $stream->close(); }, - function () {}, null, - false + false, + null, + function () {} ); } } diff --git a/tests/ab/startServer.php b/tests/ab/startServer.php index 515065a..f4fee3f 100644 --- a/tests/ab/startServer.php +++ b/tests/ab/startServer.php @@ -44,7 +44,6 @@ $server->on('request', function (\React\Http\Request $request, \React\Http\Respo function (MessageInterface $message, MessageBuffer $messageBuffer) use ($response) { $messageBuffer->sendMessage($message->getPayload(), true, $message->isBinary()); }, - [$response, 'write'], function (FrameInterface $frame) use ($response, &$parser) { switch ($frame->getOpCode()) { case Frame::OP_CLOSE: @@ -59,6 +58,7 @@ $server->on('request', function (\React\Http\Request $request, \React\Http\Respo function () use ($uException) { return $uException; }, + [$response, 'write'], $deflateOptions ); diff --git a/tests/unit/Messaging/MessageBufferTest.php b/tests/unit/Messaging/MessageBufferTest.php index 3de8561..c33ff0c 100644 --- a/tests/unit/Messaging/MessageBufferTest.php +++ b/tests/unit/Messaging/MessageBufferTest.php @@ -28,7 +28,6 @@ class MessageBufferTest extends \PHPUnit_Framework_TestCase $messageCount++; $this->assertEquals('a', $message->getPayload()); }, - function () {}, null, false ); From e91fcd67c80a5e2a374e56e48a28bce46e8d0920 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Thu, 11 Jan 2018 20:51:29 -0500 Subject: [PATCH 11/40] Force deflate window bits to 9 fixes #22 --- src/Messaging/MessageBuffer.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index e130a78..6ca82db 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -376,12 +376,16 @@ class MessageBuffer { } if ($this->deflator === null) { + $bits = (int)$this->getDeflateWindowBits(); + if ($bits === 8) { + $bits = 9; + } $this->deflator = deflate_init( ZLIB_ENCODING_RAW, [ 'level' => -1, 'memory' => 8, - 'window' => $this->getDeflateWindowBits(), + 'window' => $bits, 'strategy' => ZLIB_DEFAULT_STRATEGY ] ); From abceff5341222951e1a693b59dc4fcb72f80f899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0tefan=20Ku=C5=A1n=C3=ADr?= Date: Wed, 2 May 2018 08:45:50 +0200 Subject: [PATCH 12/40] FIX PHP Fatal error: Allowed memory size of exhausted --- src/Messaging/Frame.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Messaging/Frame.php b/src/Messaging/Frame.php index f558554..24b491e 100644 --- a/src/Messaging/Frame.php +++ b/src/Messaging/Frame.php @@ -74,7 +74,7 @@ class Frame implements FrameInterface { * @param callable<\UnderflowException> $ufExceptionFactory */ public function __construct($payload = null, $final = true, $opcode = 1, callable $ufExceptionFactory = null) { - $this->ufeg = $ufExceptionFactory ?: function($msg = '') { + $this->ufeg = $ufExceptionFactory ?: static function($msg = '') { return new \UnderflowException($msg); }; From ed16f5cba13b748ddcdb3e7d627e0eddf23699d6 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Wed, 1 May 2019 08:48:10 -0400 Subject: [PATCH 13/40] Corrected $msgBuffer issue caused by merge with master --- src/Messaging/MessageBuffer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index f5bf340..403c55c 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -149,7 +149,7 @@ class MessageBuffer { if (true !== $msgCheck) { $onControl($this->newCloseFrame($msgCheck, 'Ratchet detected an invalid UTF-8 payload'), $this); } else { - $onMessage($this->messageBuffer, $this); + $onMessage($msgBuffer, $this); } $this->messageBuffer = null; From 8944361dbe69c82da22009b267950b97c92f1650 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Tue, 10 Dec 2019 19:22:57 -0500 Subject: [PATCH 14/40] Update to latest react/socket and drop react/http for tests --- composer.json | 5 +-- tests/ab/clientRunner.php | 78 +++++++++++++++++++------------------ tests/ab/startServer.php | 81 ++++++++++++++++++++++----------------- 3 files changed, 89 insertions(+), 75 deletions(-) diff --git a/composer.json b/composer.json index 224066b..d9ff35d 100644 --- a/composer.json +++ b/composer.json @@ -25,8 +25,7 @@ "guzzlehttp/psr7": "^1.0" }, "require-dev": { - "react/http": "^0.4.1", - "react/socket-client": "^0.4.3", - "phpunit/phpunit": "4.8.*" + "phpunit/phpunit": "4.8.*", + "react/socket": "^1.3" } } diff --git a/tests/ab/clientRunner.php b/tests/ab/clientRunner.php index 0c5578a..274f82d 100644 --- a/tests/ab/clientRunner.php +++ b/tests/ab/clientRunner.php @@ -1,7 +1,14 @@ createCached('8.8.8.8', $loop); - -$factory = new \React\SocketClient\Connector($loop, $dnsResolver); +$connector = new Connector($loop); function echoStreamerFactory($conn) { - return new \Ratchet\RFC6455\Messaging\MessageBuffer( - new \Ratchet\RFC6455\Messaging\CloseFrameChecker, - function (\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($conn) { + return new MessageBuffer( + new CloseFrameChecker, + function (MessageInterface $msg) use ($conn) { /** @var Frame $frame */ foreach ($msg as $frame) { $frame->maskPayload(); } $conn->write($msg->getContents()); }, - function (\Ratchet\RFC6455\Messaging\FrameInterface $frame) use ($conn) { + function (FrameInterface $frame) use ($conn) { switch ($frame->getOpcode()) { case Frame::OP_PING: return $conn->write((new Frame($frame->getPayload(), true, Frame::OP_PONG))->maskPayload()->getContents()); @@ -42,22 +46,22 @@ function echoStreamerFactory($conn) } function getTestCases() { - global $factory; global $testServer; + global $connector; $deferred = new Deferred(); - $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred) { - $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(); + $connector->connect($testServer . ':9001')->then(function (ConnectionInterface $connection) use ($deferred) { + $cn = new ClientNegotiator(); $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001/getCaseCount')); $rawResponse = ""; $response = null; - /** @var \Ratchet\RFC6455\Messaging\Streaming\MessageBuffer $ms */ + /** @var MessageBuffer $ms */ $ms = null; - $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) { + $connection->on('data', function ($data) use ($connection, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) { if ($response === null) { $rawResponse .= $data; $pos = strpos($rawResponse, "\r\n\r\n"); @@ -67,14 +71,14 @@ function getTestCases() { $response = \GuzzleHttp\Psr7\parse_response($rawResponse); if (!$cn->validateResponse($cnRequest, $response)) { - $stream->end(); + $connection->end(); $deferred->reject(); } else { - $ms = new \Ratchet\RFC6455\Messaging\MessageBuffer( - new \Ratchet\RFC6455\Messaging\CloseFrameChecker, - function (\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($deferred, $stream) { + $ms = new MessageBuffer( + new CloseFrameChecker, + function (MessageInterface $msg) use ($deferred, $connection) { $deferred->resolve($msg->getPayload()); - $stream->close(); + $connection->close(); }, null, false @@ -89,7 +93,7 @@ function getTestCases() { } }); - $stream->write(\GuzzleHttp\Psr7\str($cnRequest)); + $connection->write(\GuzzleHttp\Psr7\str($cnRequest)); }); return $deferred->promise(); @@ -97,15 +101,15 @@ function getTestCases() { function runTest($case) { - global $factory; + global $connector; global $testServer; $casePath = "/runCase?case={$case}&agent=" . AGENT; $deferred = new Deferred(); - $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred, $casePath, $case) { - $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(); + $connector->connect($testServer . ':9001')->then(function (ConnectionInterface $connection) use ($deferred, $casePath, $case) { + $cn = new ClientNegotiator(); $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $casePath)); $rawResponse = ""; @@ -113,7 +117,7 @@ function runTest($case) $ms = null; - $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) { + $connection->on('data', function ($data) use ($connection, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) { if ($response === null) { $rawResponse .= $data; $pos = strpos($rawResponse, "\r\n\r\n"); @@ -123,10 +127,10 @@ function runTest($case) $response = \GuzzleHttp\Psr7\parse_response($rawResponse); if (!$cn->validateResponse($cnRequest, $response)) { - $stream->end(); + $connection->end(); $deferred->reject(); } else { - $ms = echoStreamerFactory($stream); + $ms = echoStreamerFactory($connection); } } } @@ -137,34 +141,34 @@ function runTest($case) } }); - $stream->on('close', function () use ($deferred) { + $connection->on('close', function () use ($deferred) { $deferred->resolve(); }); - $stream->write(\GuzzleHttp\Psr7\str($cnRequest)); + $connection->write(\GuzzleHttp\Psr7\str($cnRequest)); }); return $deferred->promise(); } function createReport() { - global $factory; + global $connector; global $testServer; $deferred = new Deferred(); - $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred) { + $connector->connect($testServer . ':9001')->then(function (ConnectionInterface $connection) use ($deferred) { $reportPath = "/updateReports?agent=" . AGENT . "&shutdownOnComplete=true"; - $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(); + $cn = new ClientNegotiator(); $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $reportPath)); $rawResponse = ""; $response = null; - /** @var \Ratchet\RFC6455\Messaging\MessageBuffer $ms */ + /** @var MessageBuffer $ms */ $ms = null; - $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) { + $connection->on('data', function ($data) use ($connection, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) { if ($response === null) { $rawResponse .= $data; $pos = strpos($rawResponse, "\r\n\r\n"); @@ -174,12 +178,12 @@ function createReport() { $response = \GuzzleHttp\Psr7\parse_response($rawResponse); if (!$cn->validateResponse($cnRequest, $response)) { - $stream->end(); + $connection->end(); $deferred->reject(); } else { - $ms = new \Ratchet\RFC6455\Messaging\MessageBuffer( - new \Ratchet\RFC6455\Messaging\CloseFrameChecker, - function (\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($deferred, $stream) { + $ms = new MessageBuffer( + new CloseFrameChecker, + function (MessageInterface $msg) use ($deferred, $stream) { $deferred->resolve($msg->getPayload()); $stream->close(); }, @@ -196,7 +200,7 @@ function createReport() { } }); - $stream->write(\GuzzleHttp\Psr7\str($cnRequest)); + $connection->write(\GuzzleHttp\Psr7\str($cnRequest)); }); return $deferred->promise(); diff --git a/tests/ab/startServer.php b/tests/ab/startServer.php index b256ec2..4baf884 100644 --- a/tests/ab/startServer.php +++ b/tests/ab/startServer.php @@ -7,49 +7,60 @@ require_once __DIR__ . "/../bootstrap.php"; $loop = \React\EventLoop\Factory::create(); -$socket = new \React\Socket\Server($loop); -$server = new \React\Http\Server($socket); +$socket = new \React\Socket\Server('127.0.0.1:9001', $loop); $closeFrameChecker = new \Ratchet\RFC6455\Messaging\CloseFrameChecker; $negotiator = new \Ratchet\RFC6455\Handshake\ServerNegotiator(new \Ratchet\RFC6455\Handshake\RequestVerifier); $uException = new \UnderflowException; -$server->on('request', function (\React\Http\Request $request, \React\Http\Response $response) use ($negotiator, $closeFrameChecker, $uException) { - $psrRequest = new \GuzzleHttp\Psr7\Request($request->getMethod(), $request->getPath(), $request->getHeaders()); - - $negotiatorResponse = $negotiator->handshake($psrRequest); - - $response->writeHead( - $negotiatorResponse->getStatusCode(), - array_merge( - $negotiatorResponse->getHeaders(), - ["Content-Length" => "0"] - ) - ); - - if ($negotiatorResponse->getStatusCode() !== 101) { - $response->end(); - return; - } - - $parser = new \Ratchet\RFC6455\Messaging\MessageBuffer($closeFrameChecker, function(MessageInterface $message) use ($response) { - $response->write($message->getContents()); - }, function(FrameInterface $frame) use ($response, &$parser) { - switch ($frame->getOpCode()) { - case Frame::OP_CLOSE: - $response->end($frame->getContents()); - break; - case Frame::OP_PING: - $response->write($parser->newFrame($frame->getPayload(), true, Frame::OP_PONG)->getContents()); - break; +$socket->on('connection', function (React\Socket\ConnectionInterface $connection) use ($negotiator, $closeFrameChecker, $uException) { + $headerComplete = false; + $buffer = ''; + $parser = null; + $connection->on('data', function ($data) use ($connection, &$parser, &$headerComplete, &$buffer, $negotiator, $closeFrameChecker, $uException) { + if ($headerComplete) { + $parser->onData($data); + return; } - }, true, function() use ($uException) { - return $uException; - }); - $request->on('data', [$parser, 'onData']); + $buffer .= $data; + $parts = explode("\r\n\r\n", $buffer); + if (count($parts) < 2) { + return; + } + $headerComplete = true; + $psrRequest = \GuzzleHttp\Psr7\parse_request($parts[0] . "\r\n\r\n"); + $negotiatorResponse = $negotiator->handshake($psrRequest); + + $negotiatorResponse = $negotiatorResponse->withAddedHeader("Content-Length", "0"); + + $connection->write(\GuzzleHttp\Psr7\str($negotiatorResponse)); + + if ($negotiatorResponse->getStatusCode() !== 101) { + $connection->end(); + return; + } + + $parser = new \Ratchet\RFC6455\Messaging\MessageBuffer($closeFrameChecker, + function (MessageInterface $message) use ($connection) { + $connection->write($message->getContents()); + }, function (FrameInterface $frame) use ($connection, &$parser) { + switch ($frame->getOpCode()) { + case Frame::OP_CLOSE: + $connection->end($frame->getContents()); + break; + case Frame::OP_PING: + $connection->write($parser->newFrame($frame->getPayload(), true, Frame::OP_PONG)->getContents()); + break; + } + }, true, function () use ($uException) { + return $uException; + }); + + array_shift($parts); + $parser->onData(implode("\r\n\r\n", $parts)); + }); }); -$socket->listen(9001, '0.0.0.0'); $loop->run(); From 8aee2208987e217933bf30e8a2a02279afb1c454 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Tue, 10 Dec 2019 19:23:45 -0500 Subject: [PATCH 15/40] Rework MessageBuffer to better handle large buffers filled with small frames --- src/Messaging/MessageBuffer.php | 56 +++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index 22f247c..a88d713 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -37,6 +37,11 @@ class MessageBuffer { */ private $checkForMask; + /** + * @var string + */ + private $leftovers; + function __construct( CloseFrameChecker $frameChecker, callable $onMessage, @@ -53,12 +58,51 @@ class MessageBuffer { $this->onMessage = $onMessage; $this->onControl = $onControl ?: function() {}; + + $this->leftovers = ''; } public function onData($data) { - while (strlen($data) > 0) { - $data = $this->processData($data); + $data = $this->leftovers . $data; + $dataLen = strlen($data); + $spyFrame = new Frame(); + + if ($dataLen < 2) { + $this->leftovers = $data; + return; } + $currentByte = 0; + $frameStart = 0; + $spyFrame->addBuffer($data[$currentByte]); + $currentByte++; + + while ($currentByte < $dataLen) { + $spyFrame->addBuffer($data[$currentByte]); + $currentByte ++; + try { + $payload_length = $spyFrame->getPayloadLength(); + $payload_start = $spyFrame->getPayloadStartingByte(); + } catch (\UnderflowException $e) { + if ($currentByte < $dataLen) { + continue; + } + break; + } + + $isCoalesced = $dataLen - $frameStart >= $payload_length + $payload_start; + + + if (!$isCoalesced) { + break; + } + $this->processData(substr($data, $frameStart, $payload_length + $payload_start)); + $spyFrame = new Frame(); + $currentByte = $frameStart + $payload_length + $payload_start; + $frameStart = $currentByte; + } + + $this->leftovers = substr($data, $frameStart); + } /** @@ -70,16 +114,12 @@ class MessageBuffer { $this->frameBuffer ?: $this->frameBuffer = $this->newFrame(); $this->frameBuffer->addBuffer($data); - if (!$this->frameBuffer->isCoalesced()) { - return ''; - } $onMessage = $this->onMessage; $onControl = $this->onControl; $this->frameBuffer = $this->frameCheck($this->frameBuffer); - $overflow = $this->frameBuffer->extractOverflow(); $this->frameBuffer->unMaskPayload(); $opcode = $this->frameBuffer->getOpcode(); @@ -108,8 +148,6 @@ class MessageBuffer { $onMessage($msgBuffer); } } - - return $overflow; } /** @@ -230,4 +268,4 @@ class MessageBuffer { public function newCloseFrame($code, $reason = '') { return $this->newFrame(pack('n', $code) . $reason, true, Frame::OP_CLOSE); } -} +} \ No newline at end of file From 58e79971e0abacc7a3d0ba09eaf0948edde0aa14 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Tue, 10 Dec 2019 19:31:01 -0500 Subject: [PATCH 16/40] New PHP versions for travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 09125d8..c65dedb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,12 @@ language: php php: - - 5.4 - - 5.5 - 5.6 - 7.0 - 7.1 - 7.2 + - 7.3 + - 7.4 before_install: - export PATH=$HOME/.local/bin:$PATH From 11a21b762817c75e165c48fe59788399d24f272b Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Tue, 10 Dec 2019 22:23:00 -0500 Subject: [PATCH 17/40] A little faster by not using Frame functions to test for frame sizes --- src/Messaging/MessageBuffer.php | 42 +++++++++++++++------------------ 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index a88d713..300b330 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -65,44 +65,40 @@ class MessageBuffer { public function onData($data) { $data = $this->leftovers . $data; $dataLen = strlen($data); - $spyFrame = new Frame(); if ($dataLen < 2) { $this->leftovers = $data; return; } - $currentByte = 0; - $frameStart = 0; - $spyFrame->addBuffer($data[$currentByte]); - $currentByte++; - while ($currentByte < $dataLen) { - $spyFrame->addBuffer($data[$currentByte]); - $currentByte ++; - try { - $payload_length = $spyFrame->getPayloadLength(); - $payload_start = $spyFrame->getPayloadStartingByte(); - } catch (\UnderflowException $e) { - if ($currentByte < $dataLen) { - continue; - } + $frameStart = 0; + while ($frameStart + 1 <= $dataLen) { + $headerSize = 2; + $payload_length = unpack('C', $data[$frameStart + 1] & "\x7f")[1]; + $isMasked = ($data[$frameStart + 1] & "\x80") === "\x80"; + $headerSize += $isMasked ? 4 : 0; + if ($payload_length > 125 && ($dataLen - $frameStart < $headerSize + 125)) { + // no point of checking - this frame is going to be bigger than the buffer is right now break; } + if ($payload_length > 125) { + $payloadLenBytes = $payload_length === 126 ? 2 : 8; + $headerSize += $payloadLenBytes; + $bytesToUpack = substr($data, $frameStart + 2, $payloadLenBytes); + $payload_length = $payload_length === 126 + ? unpack('n', $bytesToUpack)[1] + : unpack('J', $bytesToUpack)[1]; + } - $isCoalesced = $dataLen - $frameStart >= $payload_length + $payload_start; - - + $isCoalesced = $dataLen - $frameStart >= $payload_length + $headerSize; if (!$isCoalesced) { break; } - $this->processData(substr($data, $frameStart, $payload_length + $payload_start)); - $spyFrame = new Frame(); - $currentByte = $frameStart + $payload_length + $payload_start; - $frameStart = $currentByte; + $this->processData(substr($data, $frameStart, $payload_length + $headerSize)); + $frameStart = $frameStart + $payload_length + $headerSize; } $this->leftovers = substr($data, $frameStart); - } /** From 9c1df6a8e141edb4bc4bb299cfa8c92a62c95270 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Tue, 10 Dec 2019 23:05:53 -0500 Subject: [PATCH 18/40] Should be framestart + 2 --- src/Messaging/MessageBuffer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index 300b330..6b3b440 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -72,7 +72,7 @@ class MessageBuffer { } $frameStart = 0; - while ($frameStart + 1 <= $dataLen) { + while ($frameStart + 2 <= $dataLen) { $headerSize = 2; $payload_length = unpack('C', $data[$frameStart + 1] & "\x7f")[1]; $isMasked = ($data[$frameStart + 1] & "\x80") === "\x80"; From 91af8a76d57f8271e6f7b0d9fc0bfa1549200105 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Wed, 11 Dec 2019 10:18:08 -0500 Subject: [PATCH 19/40] quality of life changes Kill the php server after AB tests are done Add script runners to composer for ease of use --- composer.json | 8 ++++++++ tests/ab/run_ab_tests.sh | 2 ++ 2 files changed, 10 insertions(+) diff --git a/composer.json b/composer.json index d9ff35d..b758876 100644 --- a/composer.json +++ b/composer.json @@ -27,5 +27,13 @@ "require-dev": { "phpunit/phpunit": "4.8.*", "react/socket": "^1.3" + }, + "scripts": { + "abtests": "sh tests/ab/run_ab_tests.sh", + "phpunit": "phpunit --colors=always", + "test": [ + "@abtests", + "@phpunit" + ] } } diff --git a/tests/ab/run_ab_tests.sh b/tests/ab/run_ab_tests.sh index 8fa9ced..f06a343 100644 --- a/tests/ab/run_ab_tests.sh +++ b/tests/ab/run_ab_tests.sh @@ -9,3 +9,5 @@ sleep 2 php startServer.php & sleep 3 wstest -m fuzzingclient -s fuzzingclient.json +sleep 1 +kill $(ps aux | grep 'php startServer.php' | awk '{print $2}' | head -n 1) From 830e2f561e25970d02b1605ef333a38100875614 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Wed, 11 Dec 2019 13:27:42 -0500 Subject: [PATCH 20/40] Allow limits for maximum payload --- src/Messaging/Message.php | 19 ++- src/Messaging/MessageBuffer.php | 62 ++++++++- tests/unit/Messaging/MessageBufferTest.php | 154 +++++++++++++++++++++ 3 files changed, 223 insertions(+), 12 deletions(-) diff --git a/src/Messaging/Message.php b/src/Messaging/Message.php index 4f3b014..33a6337 100644 --- a/src/Messaging/Message.php +++ b/src/Messaging/Message.php @@ -7,8 +7,14 @@ class Message implements \IteratorAggregate, MessageInterface { */ private $_frames; + /** + * @var int + */ + private $len; + public function __construct() { $this->_frames = new \SplDoublyLinkedList; + $this->len = 0; } public function getIterator() { @@ -39,6 +45,7 @@ class Message implements \IteratorAggregate, MessageInterface { * {@inheritdoc} */ public function addFrame(FrameInterface $fragment) { + $this->len += $fragment->getPayloadLength(); $this->_frames->push($fragment); return $this; @@ -59,17 +66,7 @@ class Message implements \IteratorAggregate, MessageInterface { * {@inheritdoc} */ public function getPayloadLength() { - $len = 0; - - foreach ($this->_frames as $frame) { - try { - $len += $frame->getPayloadLength(); - } catch (\UnderflowException $e) { - // Not an error, want the current amount buffered - } - } - - return $len; + return $this->len; } /** diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index 6b3b440..9540acf 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -42,12 +42,24 @@ class MessageBuffer { */ private $leftovers; + /** + * @var int + */ + private $maxMessagePayloadSize; + + /** + * @var int + */ + private $maxFramePayloadSize; + function __construct( CloseFrameChecker $frameChecker, callable $onMessage, callable $onControl = null, $expectMask = true, - $exceptionFactory = null + $exceptionFactory = null, + $maxMessagePayloadSize = null, // null for default - zero for no limit + $maxFramePayloadSize = null // null for default - zero for no limit ) { $this->closeFrameChecker = $frameChecker; $this->checkForMask = (bool)$expectMask; @@ -60,6 +72,31 @@ class MessageBuffer { $this->onControl = $onControl ?: function() {}; $this->leftovers = ''; + + $memory_limit = \trim(\ini_get('memory_limit')); + $memory_limit_bytes = 0; + if ($memory_limit !== '') { + $shifty = ['k' => 0, 'm' => 10, 'g' => 20]; + $multiplier = strtolower($memory_limit)[-1]; + $memory_limit = (int)$memory_limit; + $memory_limit_bytes = in_array($multiplier, array_keys($shifty), true) ? $memory_limit * 1024 << $shifty[$multiplier] : $memory_limit; + } + if ($maxMessagePayloadSize === null) { + $maxMessagePayloadSize = $memory_limit_bytes / 4; + } + if ($maxFramePayloadSize === null) { + $maxFramePayloadSize = $memory_limit_bytes / 4; + } + + if (!is_int($maxFramePayloadSize) || $maxFramePayloadSize > 0x7FFFFFFFFFFFFFFF || $maxFramePayloadSize < 0) { // this should be interesting on non-64 bit systems + throw New \InvalidArgumentException('maxFramePayloadSize is not valid'); + } + $this->maxFramePayloadSize = $maxFramePayloadSize; + + if (!is_int($maxMessagePayloadSize) || $maxMessagePayloadSize > 0x7FFFFFFFFFFFFFFF || $maxMessagePayloadSize < 0) { + throw New \InvalidArgumentException('maxMessagePayloadSize is not valid'); + } + $this->maxMessagePayloadSize = $maxMessagePayloadSize; } public function onData($data) { @@ -90,6 +127,29 @@ class MessageBuffer { : unpack('J', $bytesToUpack)[1]; } + $closeFrame = null; + + if ($payload_length < 0) { + // this can happen when unpacking in php + $closeFrame = $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Invalid frame length'); + } + + if (!$closeFrame && $this->maxFramePayloadSize > 1 && $payload_length > $this->maxFramePayloadSize) { + $closeFrame = $this->newCloseFrame(Frame::CLOSE_TOO_BIG, 'Maximum frame size exceeded'); + } + + if (!$closeFrame && $this->maxMessagePayloadSize > 0 + && $payload_length + ($this->messageBuffer ? $this->messageBuffer->getPayloadLength() : 0) > $this->maxMessagePayloadSize) { + $closeFrame = $this->newCloseFrame(Frame::CLOSE_TOO_BIG, 'Maximum message size exceeded'); + } + + if ($closeFrame !== null) { + $onControl = $this->onControl; + $onControl($closeFrame); + $this->leftovers = ''; + return; + } + $isCoalesced = $dataLen - $frameStart >= $payload_length + $headerSize; if (!$isCoalesced) { break; diff --git a/tests/unit/Messaging/MessageBufferTest.php b/tests/unit/Messaging/MessageBufferTest.php index 567afa2..eefc5dc 100644 --- a/tests/unit/Messaging/MessageBufferTest.php +++ b/tests/unit/Messaging/MessageBufferTest.php @@ -69,4 +69,158 @@ class MessageBufferTest extends \PHPUnit_Framework_TestCase $this->assertTrue($bReceived); } + + public function testInvalidFrameLength() { + $frame = new Frame(str_repeat('a', 200), true, Frame::OP_TEXT); + + $frameRaw = $frame->getContents(); + + $frameRaw[1] = "\x7f"; // 127 in the first spot + + $frameRaw[2] = "\xff"; // this will unpack to -1 + $frameRaw[3] = "\xff"; + $frameRaw[4] = "\xff"; + $frameRaw[5] = "\xff"; + $frameRaw[6] = "\xff"; + $frameRaw[7] = "\xff"; + $frameRaw[8] = "\xff"; + $frameRaw[9] = "\xff"; + + /** @var Frame $controlFrame */ + $controlFrame = null; + $messageCount = 0; + + $messageBuffer = new MessageBuffer( + new CloseFrameChecker(), + function (Message $message) use (&$messageCount) { + $messageCount++; + }, + function (Frame $frame) use (&$controlFrame) { + $this->assertNull($controlFrame); + $controlFrame = $frame; + }, + false, + null, + 0, + 10 + ); + + $messageBuffer->onData($frameRaw); + + $this->assertEquals(0, $messageCount); + $this->assertTrue($controlFrame instanceof Frame); + $this->assertEquals(Frame::OP_CLOSE, $controlFrame->getOpcode()); + $this->assertEquals([Frame::CLOSE_PROTOCOL], array_merge(unpack('n*', substr($controlFrame->getPayload(), 0, 2)))); + + } + + public function testFrameLengthTooBig() { + $frame = new Frame(str_repeat('a', 200), true, Frame::OP_TEXT); + + $frameRaw = $frame->getContents(); + + $frameRaw[1] = "\x7f"; // 127 in the first spot + + $frameRaw[2] = "\x7f"; // this will unpack to -1 + $frameRaw[3] = "\xff"; + $frameRaw[4] = "\xff"; + $frameRaw[5] = "\xff"; + $frameRaw[6] = "\xff"; + $frameRaw[7] = "\xff"; + $frameRaw[8] = "\xff"; + $frameRaw[9] = "\xff"; + + /** @var Frame $controlFrame */ + $controlFrame = null; + $messageCount = 0; + + $messageBuffer = new MessageBuffer( + new CloseFrameChecker(), + function (Message $message) use (&$messageCount) { + $messageCount++; + }, + function (Frame $frame) use (&$controlFrame) { + $this->assertNull($controlFrame); + $controlFrame = $frame; + }, + false, + null, + 0, + 10 + ); + + $messageBuffer->onData($frameRaw); + + $this->assertEquals(0, $messageCount); + $this->assertTrue($controlFrame instanceof Frame); + $this->assertEquals(Frame::OP_CLOSE, $controlFrame->getOpcode()); + $this->assertEquals([Frame::CLOSE_TOO_BIG], array_merge(unpack('n*', substr($controlFrame->getPayload(), 0, 2)))); + } + + public function testFrameLengthBiggerThanMaxMessagePayload() { + $frame = new Frame(str_repeat('a', 200), true, Frame::OP_TEXT); + + $frameRaw = $frame->getContents(); + + /** @var Frame $controlFrame */ + $controlFrame = null; + $messageCount = 0; + + $messageBuffer = new MessageBuffer( + new CloseFrameChecker(), + function (Message $message) use (&$messageCount) { + $messageCount++; + }, + function (Frame $frame) use (&$controlFrame) { + $this->assertNull($controlFrame); + $controlFrame = $frame; + }, + false, + null, + 100, + 0 + ); + + $messageBuffer->onData($frameRaw); + + $this->assertEquals(0, $messageCount); + $this->assertTrue($controlFrame instanceof Frame); + $this->assertEquals(Frame::OP_CLOSE, $controlFrame->getOpcode()); + $this->assertEquals([Frame::CLOSE_TOO_BIG], array_merge(unpack('n*', substr($controlFrame->getPayload(), 0, 2)))); + } + + public function testSecondFrameLengthPushesPastMaxMessagePayload() { + $frame = new Frame(str_repeat('a', 200), false, Frame::OP_TEXT); + $firstFrameRaw = $frame->getContents(); + $frame = new Frame(str_repeat('b', 200), true, Frame::OP_TEXT); + $secondFrameRaw = $frame->getContents(); + + /** @var Frame $controlFrame */ + $controlFrame = null; + $messageCount = 0; + + $messageBuffer = new MessageBuffer( + new CloseFrameChecker(), + function (Message $message) use (&$messageCount) { + $messageCount++; + }, + function (Frame $frame) use (&$controlFrame) { + $this->assertNull($controlFrame); + $controlFrame = $frame; + }, + false, + null, + 300, + 0 + ); + + $messageBuffer->onData($firstFrameRaw); + // only put part of the second frame in to watch it fail fast + $messageBuffer->onData(substr($secondFrameRaw, 0, 150)); + + $this->assertEquals(0, $messageCount); + $this->assertTrue($controlFrame instanceof Frame); + $this->assertEquals(Frame::OP_CLOSE, $controlFrame->getOpcode()); + $this->assertEquals([Frame::CLOSE_TOO_BIG], array_merge(unpack('n*', substr($controlFrame->getPayload(), 0, 2)))); + } } \ No newline at end of file From 921f838255b81eba5ee190fcaf21a2c2ec9e0c1c Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Wed, 11 Dec 2019 14:47:53 -0500 Subject: [PATCH 21/40] Support for unsupported PHP versions --- src/Messaging/MessageBuffer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index 9540acf..51eaad0 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -77,7 +77,7 @@ class MessageBuffer { $memory_limit_bytes = 0; if ($memory_limit !== '') { $shifty = ['k' => 0, 'm' => 10, 'g' => 20]; - $multiplier = strtolower($memory_limit)[-1]; + $multiplier = strlen($memory_limit) > 1 ? substr(strtolower($memory_limit), -1) : ''; $memory_limit = (int)$memory_limit; $memory_limit_bytes = in_array($multiplier, array_keys($shifty), true) ? $memory_limit * 1024 << $shifty[$multiplier] : $memory_limit; } From c004fa7e6452a7f65d931996400a02286f728da7 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Thu, 12 Dec 2019 11:37:11 -0500 Subject: [PATCH 22/40] Code formatting --- src/Messaging/MessageBuffer.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index 51eaad0..1d39517 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -105,6 +105,7 @@ class MessageBuffer { if ($dataLen < 2) { $this->leftovers = $data; + return; } @@ -147,6 +148,7 @@ class MessageBuffer { $onControl = $this->onControl; $onControl($closeFrame); $this->leftovers = ''; + return; } From cfc9049d13e7c37ef2f3f38f49bc32b35f503417 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Thu, 12 Dec 2019 13:59:55 -0500 Subject: [PATCH 23/40] Make memory limit testable --- src/Messaging/MessageBuffer.php | 29 ++++++++---- tests/unit/Messaging/MessageBufferTest.php | 55 ++++++++++++++++++++++ 2 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index 1d39517..356986f 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -73,14 +73,7 @@ class MessageBuffer { $this->leftovers = ''; - $memory_limit = \trim(\ini_get('memory_limit')); - $memory_limit_bytes = 0; - if ($memory_limit !== '') { - $shifty = ['k' => 0, 'm' => 10, 'g' => 20]; - $multiplier = strlen($memory_limit) > 1 ? substr(strtolower($memory_limit), -1) : ''; - $memory_limit = (int)$memory_limit; - $memory_limit_bytes = in_array($multiplier, array_keys($shifty), true) ? $memory_limit * 1024 << $shifty[$multiplier] : $memory_limit; - } + $memory_limit_bytes = static::getMemoryLimit(); if ($maxMessagePayloadSize === null) { $maxMessagePayloadSize = $memory_limit_bytes / 4; } @@ -326,4 +319,24 @@ class MessageBuffer { public function newCloseFrame($code, $reason = '') { return $this->newFrame(pack('n', $code) . $reason, true, Frame::OP_CLOSE); } + + /** + * This is a separate function for testing purposes + * $memory_limit is only used for testing + * + * @param null|string $memory_limit + * @return int + */ + private static function getMemoryLimit($memory_limit = null) { + $memory_limit = $memory_limit === null ? \trim(\ini_get('memory_limit')) : $memory_limit; + $memory_limit_bytes = 0; + if ($memory_limit !== '') { + $shifty = ['k' => 0, 'm' => 10, 'g' => 20]; + $multiplier = strlen($memory_limit) > 1 ? substr(strtolower($memory_limit), -1) : ''; + $memory_limit = (int)$memory_limit; + $memory_limit_bytes = in_array($multiplier, array_keys($shifty), true) ? $memory_limit * 1024 << $shifty[$multiplier] : $memory_limit; + } + + return $memory_limit_bytes; + } } \ No newline at end of file diff --git a/tests/unit/Messaging/MessageBufferTest.php b/tests/unit/Messaging/MessageBufferTest.php index eefc5dc..261878b 100644 --- a/tests/unit/Messaging/MessageBufferTest.php +++ b/tests/unit/Messaging/MessageBufferTest.php @@ -223,4 +223,59 @@ class MessageBufferTest extends \PHPUnit_Framework_TestCase $this->assertEquals(Frame::OP_CLOSE, $controlFrame->getOpcode()); $this->assertEquals([Frame::CLOSE_TOO_BIG], array_merge(unpack('n*', substr($controlFrame->getPayload(), 0, 2)))); } + + /** + * Some test cases from memory limit inspired by https://github.com/BrandEmbassy/php-memory + * + * Here is the license for that project: + * MIT License + * + * Copyright (c) 2018 Brand Embassy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + /** + * @dataProvider phpConfigurationProvider + * + * @param string $phpConfigurationValue + * @param int $expectedLimit + */ + public function testMemoryLimits($phpConfigurationValue, $expectedLimit) { + $method = new \ReflectionMethod('Ratchet\RFC6455\Messaging\MessageBuffer', 'getMemoryLimit'); + $method->setAccessible(true); + $actualLimit = $method->invoke(null, $phpConfigurationValue); + + $this->assertSame($expectedLimit, $actualLimit); + } + + public function phpConfigurationProvider() { + return [ + 'without unit type, just bytes' => ['500', 500], + '1 GB with big "G"' => ['1G', 1 * 1024 * 1024 * 1024], + '128 MB with big "M"' => ['128M', 128 * 1024 * 1024], + '128 MB with small "m"' => ['128m', 128 * 1024 * 1024], + '24 kB with small "k"' => ['24k', 24 * 1024], + '2 GB with small "g"' => ['2g', 2 * 1024 * 1024 * 1024], + 'unlimited memory' => ['-1', -1], + 'invalid float value' => ['2.5M', 2 * 1024 * 1024], + 'empty value' => ['', 0] + ]; + } } \ No newline at end of file From bbdd346c1c47c571142ed9ae0dff40345541b42a Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Sat, 14 Dec 2019 12:52:19 -0500 Subject: [PATCH 24/40] Test resulting settings from different memory_limits + more tests --- src/Messaging/MessageBuffer.php | 3 +- tests/unit/Messaging/MessageBufferTest.php | 88 +++++++++++++++++++++- 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index 356986f..e1d0f6f 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -74,6 +74,7 @@ class MessageBuffer { $this->leftovers = ''; $memory_limit_bytes = static::getMemoryLimit(); + if ($maxMessagePayloadSize === null) { $maxMessagePayloadSize = $memory_limit_bytes / 4; } @@ -337,6 +338,6 @@ class MessageBuffer { $memory_limit_bytes = in_array($multiplier, array_keys($shifty), true) ? $memory_limit * 1024 << $shifty[$multiplier] : $memory_limit; } - return $memory_limit_bytes; + return $memory_limit_bytes < 0 ? 0 : $memory_limit_bytes; } } \ No newline at end of file diff --git a/tests/unit/Messaging/MessageBufferTest.php b/tests/unit/Messaging/MessageBufferTest.php index 261878b..ee36b38 100644 --- a/tests/unit/Messaging/MessageBufferTest.php +++ b/tests/unit/Messaging/MessageBufferTest.php @@ -273,9 +273,93 @@ class MessageBufferTest extends \PHPUnit_Framework_TestCase '128 MB with small "m"' => ['128m', 128 * 1024 * 1024], '24 kB with small "k"' => ['24k', 24 * 1024], '2 GB with small "g"' => ['2g', 2 * 1024 * 1024 * 1024], - 'unlimited memory' => ['-1', -1], + 'unlimited memory' => ['-1', 0], 'invalid float value' => ['2.5M', 2 * 1024 * 1024], - 'empty value' => ['', 0] + 'empty value' => ['', 0], + 'invalid ini setting' => ['whatever it takes', 0] ]; } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidMaxFramePayloadSizes() { + $messageBuffer = new MessageBuffer( + new CloseFrameChecker(), + function (Message $message) {}, + function (Frame $frame) {}, + false, + null, + 0, + 0x8000000000000000 + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidMaxMessagePayloadSizes() { + $messageBuffer = new MessageBuffer( + new CloseFrameChecker(), + function (Message $message) {}, + function (Frame $frame) {}, + false, + null, + 0x8000000000000000, + 0 + ); + } + + /** + * @dataProvider phpConfigurationProvider + * + * @param string $phpConfigurationValue + * @param int $expectedLimit + * + * @runInSeparateProcess + */ + public function testIniSizes($phpConfigurationValue, $expectedLimit) { + ini_set('memory_limit', $phpConfigurationValue); + $messageBuffer = new MessageBuffer( + new CloseFrameChecker(), + function (Message $message) {}, + function (Frame $frame) {}, + false, + null + ); + + if ($expectedLimit === -1) { + $expectedLimit = 0; + } + + $prop = new \ReflectionProperty($messageBuffer, 'maxMessagePayloadSize'); + $prop->setAccessible(true); + $this->assertEquals($expectedLimit / 4, $prop->getValue($messageBuffer)); + + $prop = new \ReflectionProperty($messageBuffer, 'maxFramePayloadSize'); + $prop->setAccessible(true); + $this->assertEquals($expectedLimit / 4, $prop->getValue($messageBuffer)); + } + + /** + * @runInSeparateProcess + */ + public function testInvalidIniSize() { + ini_set('memory_limit', 'lots of memory'); + $messageBuffer = new MessageBuffer( + new CloseFrameChecker(), + function (Message $message) {}, + function (Frame $frame) {}, + false, + null + ); + + $prop = new \ReflectionProperty($messageBuffer, 'maxMessagePayloadSize'); + $prop->setAccessible(true); + $this->assertEquals(0, $prop->getValue($messageBuffer)); + + $prop = new \ReflectionProperty($messageBuffer, 'maxFramePayloadSize'); + $prop->setAccessible(true); + $this->assertEquals(0, $prop->getValue($messageBuffer)); + } } \ No newline at end of file From 9d6d7d01d3fa8b11d7133f28a0e72d887d487ce1 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Sat, 14 Dec 2019 13:10:40 -0500 Subject: [PATCH 25/40] Don't run ini_set tests in php 5 because it crashes --- tests/unit/Messaging/MessageBufferTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/Messaging/MessageBufferTest.php b/tests/unit/Messaging/MessageBufferTest.php index ee36b38..9d444bc 100644 --- a/tests/unit/Messaging/MessageBufferTest.php +++ b/tests/unit/Messaging/MessageBufferTest.php @@ -317,6 +317,7 @@ class MessageBufferTest extends \PHPUnit_Framework_TestCase * @param int $expectedLimit * * @runInSeparateProcess + * @requires PHP >= 7.0 */ public function testIniSizes($phpConfigurationValue, $expectedLimit) { ini_set('memory_limit', $phpConfigurationValue); @@ -343,6 +344,7 @@ class MessageBufferTest extends \PHPUnit_Framework_TestCase /** * @runInSeparateProcess + * @requires PHP >= 7.0 */ public function testInvalidIniSize() { ini_set('memory_limit', 'lots of memory'); From e7b72440c939a1ba828187b420909e5d99001de4 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Sat, 14 Dec 2019 14:26:11 -0500 Subject: [PATCH 26/40] Fix requires annotation to skip some tests on 5.6 --- tests/unit/Messaging/MessageBufferTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/Messaging/MessageBufferTest.php b/tests/unit/Messaging/MessageBufferTest.php index 9d444bc..b5925ab 100644 --- a/tests/unit/Messaging/MessageBufferTest.php +++ b/tests/unit/Messaging/MessageBufferTest.php @@ -317,7 +317,7 @@ class MessageBufferTest extends \PHPUnit_Framework_TestCase * @param int $expectedLimit * * @runInSeparateProcess - * @requires PHP >= 7.0 + * @requires PHP 7.0 */ public function testIniSizes($phpConfigurationValue, $expectedLimit) { ini_set('memory_limit', $phpConfigurationValue); @@ -344,7 +344,7 @@ class MessageBufferTest extends \PHPUnit_Framework_TestCase /** * @runInSeparateProcess - * @requires PHP >= 7.0 + * @requires PHP 7.0 */ public function testInvalidIniSize() { ini_set('memory_limit', 'lots of memory'); From e861242e1d3bf3bfccaac7f647bb955f95c3afd7 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 15 Dec 2019 05:17:35 -0500 Subject: [PATCH 27/40] Provide value in error for clarity --- src/Messaging/MessageBuffer.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index e1d0f6f..9b1a23b 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -83,12 +83,12 @@ class MessageBuffer { } if (!is_int($maxFramePayloadSize) || $maxFramePayloadSize > 0x7FFFFFFFFFFFFFFF || $maxFramePayloadSize < 0) { // this should be interesting on non-64 bit systems - throw New \InvalidArgumentException('maxFramePayloadSize is not valid'); + throw new \InvalidArgumentException($maxFramePayloadSize . ' is not a valid maxFramePayloadSize'); } $this->maxFramePayloadSize = $maxFramePayloadSize; if (!is_int($maxMessagePayloadSize) || $maxMessagePayloadSize > 0x7FFFFFFFFFFFFFFF || $maxMessagePayloadSize < 0) { - throw New \InvalidArgumentException('maxMessagePayloadSize is not valid'); + throw new \InvalidArgumentException($maxMessagePayloadSize . 'is not a valid maxMessagePayloadSize'); } $this->maxMessagePayloadSize = $maxMessagePayloadSize; } @@ -340,4 +340,4 @@ class MessageBuffer { return $memory_limit_bytes < 0 ? 0 : $memory_limit_bytes; } -} \ No newline at end of file +} From 2159ed40f9eb487d8604667ce67d5a40cac548af Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Wed, 22 Jan 2020 01:00:24 -0500 Subject: [PATCH 28/40] Requested changes for readability and style --- src/Handshake/ClientNegotiator.php | 2 +- src/Handshake/PermessageDeflateOptions.php | 28 ++++++++++------------ src/Handshake/ServerNegotiator.php | 2 +- src/Messaging/MessageBuffer.php | 10 ++++---- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/Handshake/ClientNegotiator.php b/src/Handshake/ClientNegotiator.php index 4acf5e4..c6e5b3f 100644 --- a/src/Handshake/ClientNegotiator.php +++ b/src/Handshake/ClientNegotiator.php @@ -34,7 +34,7 @@ class ClientNegotiator { // https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18 $supported = PermessageDeflateOptions::permessageDeflateSupported(); if (!$supported) { - if ($perMessageDeflateOptions->getDeflate()) { + if ($perMessageDeflateOptions->isEnabled()) { trigger_error('permessage-deflate is being disabled because it is not support by your PHP version.', E_USER_NOTICE); } $perMessageDeflateOptions = PermessageDeflateOptions::createDisabled(); diff --git a/src/Handshake/PermessageDeflateOptions.php b/src/Handshake/PermessageDeflateOptions.php index ff983c1..4dde2f1 100644 --- a/src/Handshake/PermessageDeflateOptions.php +++ b/src/Handshake/PermessageDeflateOptions.php @@ -12,7 +12,7 @@ final class PermessageDeflateOptions /* this is a private instead of const for 5.4 compatibility */ private static $VALID_BITS = ['8', '9', '10', '11', '12', '13', '14', '15']; - private $deflate = false; + private $deflateEnabled = false; private $server_no_context_takeover; private $client_no_context_takeover; @@ -22,12 +22,13 @@ final class PermessageDeflateOptions private function __construct() { } public static function createDefault() { - $new = new static(); - $new->deflate = true; - $new->client_max_window_bits = self::MAX_WINDOW_BITS; + $new = new static(); + $new->deflateEnabled = true; + $new->client_max_window_bits = self::MAX_WINDOW_BITS; $new->client_no_context_takeover = false; - $new->server_max_window_bits = self::MAX_WINDOW_BITS; + $new->server_max_window_bits = self::MAX_WINDOW_BITS; $new->server_no_context_takeover = false; + return $new; } @@ -96,8 +97,8 @@ final class PermessageDeflateOptions } array_shift($parts); - $options = new static(); - $options->deflate = true; + $options = new static(); + $options->deflateEnabled = true; foreach ($parts as $part) { $kv = explode('=', $part); $key = $kv[0]; @@ -152,11 +153,6 @@ final class PermessageDeflateOptions return $optionSets; } -// public static function validateResponseToRequest(ResponseInterface $response, RequestInterface $request) { -// $requestOptions = static::fromRequestOrResponse($request); -// $responseOptions = static::fromRequestOrResponse($response); -// } - /** * @return mixed */ @@ -192,9 +188,9 @@ final class PermessageDeflateOptions /** * @return bool */ - public function getDeflate() + public function isEnabled() { - return $this->deflate; + return $this->deflateEnabled; } /** @@ -203,7 +199,7 @@ final class PermessageDeflateOptions */ public function addHeaderToResponse(ResponseInterface $response) { - if (!$this->deflate) { + if (!$this->deflateEnabled) { return $response; } @@ -225,7 +221,7 @@ final class PermessageDeflateOptions } public function addHeaderToRequest(RequestInterface $request) { - if (!$this->deflate) { + if (!$this->deflateEnabled) { return $request; } diff --git a/src/Handshake/ServerNegotiator.php b/src/Handshake/ServerNegotiator.php index 3059f65..64cc415 100644 --- a/src/Handshake/ServerNegotiator.php +++ b/src/Handshake/ServerNegotiator.php @@ -126,7 +126,7 @@ class ServerNegotiator implements NegotiatorInterface { return new Response(400, [], null, '1.1', $e->getMessage()); } - if ($this->enablePerMessageDeflate && $perMessageDeflateRequest->getDeflate()) { + if ($this->enablePerMessageDeflate && $perMessageDeflateRequest->isEnabled()) { $response = $perMessageDeflateRequest->addHeaderToResponse($response); } diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index 63f9f47..e6bf1ce 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -62,7 +62,7 @@ class MessageBuffer { /** * @var bool */ - private $deflate = false; + private $deflateEnabled = false; /** * @var int @@ -104,9 +104,9 @@ class MessageBuffer { $this->permessageDeflateOptions = $permessageDeflateOptions ?: PermessageDeflateOptions::createDisabled(); - $this->deflate = $this->permessageDeflateOptions->getDeflate(); + $this->deflateEnabled = $this->permessageDeflateOptions->isEnabled(); - if ($this->deflate && !is_callable($this->sender)) { + if ($this->deflateEnabled && !is_callable($this->sender)) { throw new \InvalidArgumentException('sender must be set when deflate is enabled'); } @@ -263,7 +263,7 @@ class MessageBuffer { * @return \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface */ public function frameCheck(FrameInterface $frame) { - if ((false !== $frame->getRsv1() && !$this->deflate) || + if ((false !== $frame->getRsv1() && !$this->deflateEnabled) || false !== $frame->getRsv2() || false !== $frame->getRsv3() ) { @@ -381,7 +381,7 @@ class MessageBuffer { throw new \Exception('To send frames using the MessageBuffer, sender must be set.'); } - if ($this->deflate && + if ($this->deflateEnabled && ($frame->getOpcode() === Frame::OP_TEXT || $frame->getOpcode() === Frame::OP_BINARY)) { $frame = $this->deflateFrame($frame); } From 1cb14114c36eae7ee3017778d3d22a6bda502bac Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Tue, 10 Mar 2020 09:26:05 -0400 Subject: [PATCH 29/40] Change how disabled/supported/enabled is handled for deflate --- src/Handshake/ClientNegotiator.php | 12 +++++++----- src/Handshake/PermessageDeflateOptions.php | 2 +- src/Handshake/ServerNegotiator.php | 6 ++---- tests/ab/clientRunner.php | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Handshake/ClientNegotiator.php b/src/Handshake/ClientNegotiator.php index c6e5b3f..c32a1cf 100644 --- a/src/Handshake/ClientNegotiator.php +++ b/src/Handshake/ClientNegotiator.php @@ -32,11 +32,13 @@ class ClientNegotiator { // https://bugs.php.net/bug.php?id=73373 // https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18 - $supported = PermessageDeflateOptions::permessageDeflateSupported(); - if (!$supported) { - if ($perMessageDeflateOptions->isEnabled()) { - trigger_error('permessage-deflate is being disabled because it is not support by your PHP version.', E_USER_NOTICE); - } + if ($perMessageDeflateOptions->isEnabled() && + !PermessageDeflateOptions::permessageDeflateSupported()) { + trigger_error('permessage-deflate is being disabled because it is not support by your PHP version.', E_USER_NOTICE); + $perMessageDeflateOptions = PermessageDeflateOptions::createDisabled(); + } + if ($perMessageDeflateOptions->isEnabled() && !function_exists('deflate_add')) { + trigger_error('permessage-deflate is being disabled because you do not have the zlib extension.', E_USER_NOTICE); $perMessageDeflateOptions = PermessageDeflateOptions::createDisabled(); } diff --git a/src/Handshake/PermessageDeflateOptions.php b/src/Handshake/PermessageDeflateOptions.php index 4dde2f1..7a56750 100644 --- a/src/Handshake/PermessageDeflateOptions.php +++ b/src/Handshake/PermessageDeflateOptions.php @@ -21,7 +21,7 @@ final class PermessageDeflateOptions private function __construct() { } - public static function createDefault() { + public static function createEnabled() { $new = new static(); $new->deflateEnabled = true; $new->client_max_window_bits = self::MAX_WINDOW_BITS; diff --git a/src/Handshake/ServerNegotiator.php b/src/Handshake/ServerNegotiator.php index 64cc415..e4ce79b 100644 --- a/src/Handshake/ServerNegotiator.php +++ b/src/Handshake/ServerNegotiator.php @@ -26,12 +26,10 @@ class ServerNegotiator implements NegotiatorInterface { // https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18 $supported = PermessageDeflateOptions::permessageDeflateSupported(); if ($enablePerMessageDeflate && !$supported) { - trigger_error('permessage-deflate is being disabled because it is not support by your PHP version.', E_USER_NOTICE); - $enablePerMessageDeflate = false; + throw new \Exception('permessage-deflate is not supported by your PHP version (need >=7.1.4 or >=7.0.18).'); } if ($enablePerMessageDeflate && !function_exists('deflate_add')) { - trigger_error('permessage-deflate is being disabled because you do not have the zlib extension.', E_USER_NOTICE); - $enablePerMessageDeflate = false; + throw new \Exception('permessage-deflate is not supported because you do not have the zlib extension.'); } $this->enablePerMessageDeflate = $enablePerMessageDeflate; diff --git a/tests/ab/clientRunner.php b/tests/ab/clientRunner.php index 9bd2873..1c28d74 100644 --- a/tests/ab/clientRunner.php +++ b/tests/ab/clientRunner.php @@ -107,7 +107,7 @@ function getTestCases() { return $deferred->promise(); } -$cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(PermessageDeflateOptions::createDefault()); +$cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(PermessageDeflateOptions::createEnabled()); function runTest($case) { @@ -120,7 +120,7 @@ function runTest($case) $deferred = new Deferred(); $connector->connect($testServer . ':9001')->then(function (ConnectionInterface $connection) use ($deferred, $casePath, $case) { - $cn = new ClientNegotiator(PermessageDeflateOptions::createDefault()); + $cn = new ClientNegotiator(PermessageDeflateOptions::createEnabled()); $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $casePath)); $rawResponse = ""; From 7ef269aca107c8f1e34d52d79ce9f6f3f54ad217 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Thu, 9 Apr 2020 17:39:37 -0400 Subject: [PATCH 30/40] Have travis use docker for wstest while testing --- .travis.yml | 11 ++++++++--- composer.json | 2 +- tests/ab/clientRunner.php | 11 ++++++++--- tests/ab/fuzzingclient.json | 25 ++---------------------- tests/ab/fuzzingserver.json | 23 +--------------------- tests/ab/run_ab_tests.sh | 39 +++++++++++++++++++++++++++++++------ tests/ab/startServer.php | 14 ++++++++++--- 7 files changed, 64 insertions(+), 61 deletions(-) diff --git a/.travis.yml b/.travis.yml index c65dedb..a7be1be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: php +services: docker + php: - 5.6 - 7.0 @@ -7,11 +9,14 @@ php: - 7.2 - 7.3 - 7.4 + - nightly + +matrix: + allow_failures: + - php: nightly before_install: - - export PATH=$HOME/.local/bin:$PATH - - pip install --user autobahntestsuite - - pip list --user autobahntestsuite + - docker pull crossbario/autobahn-testsuite before_script: - composer install diff --git a/composer.json b/composer.json index b758876..c7f22c7 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "guzzlehttp/psr7": "^1.0" }, "require-dev": { - "phpunit/phpunit": "4.8.*", + "phpunit/phpunit": "5.7.*", "react/socket": "^1.3" }, "scripts": { diff --git a/tests/ab/clientRunner.php b/tests/ab/clientRunner.php index 1c28d74..d41a645 100644 --- a/tests/ab/clientRunner.php +++ b/tests/ab/clientRunner.php @@ -107,7 +107,8 @@ function getTestCases() { return $deferred->promise(); } -$cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(PermessageDeflateOptions::createEnabled()); +$cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator( + PermessageDeflateOptions::permessageDeflateSupported() ? PermessageDeflateOptions::createEnabled() : null); function runTest($case) { @@ -120,7 +121,8 @@ function runTest($case) $deferred = new Deferred(); $connector->connect($testServer . ':9001')->then(function (ConnectionInterface $connection) use ($deferred, $casePath, $case) { - $cn = new ClientNegotiator(PermessageDeflateOptions::createEnabled()); + $cn = new ClientNegotiator( + PermessageDeflateOptions::permessageDeflateSupported() ? PermessageDeflateOptions::createEnabled() : null); $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $casePath)); $rawResponse = ""; @@ -178,7 +180,9 @@ function createReport() { $deferred = new Deferred(); $connector->connect($testServer . ':9001')->then(function (ConnectionInterface $connection) use ($deferred) { - $reportPath = "/updateReports?agent=" . AGENT . "&shutdownOnComplete=true"; + // $reportPath = "/updateReports?agent=" . AGENT . "&shutdownOnComplete=true"; + // we will stop it using docker now instead of just shutting down + $reportPath = "/updateReports?agent=" . AGENT; $cn = new ClientNegotiator(); $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $reportPath)); @@ -242,6 +246,7 @@ getTestCases()->then(function ($count) use ($loop) { $allDeferred->resolve(); return; } + echo "Running test $i/$count\n"; runTest($i)->then($runNextCase); }; diff --git a/tests/ab/fuzzingclient.json b/tests/ab/fuzzingclient.json index c43d414..fc3869b 100644 --- a/tests/ab/fuzzingclient.json +++ b/tests/ab/fuzzingclient.json @@ -5,32 +5,11 @@ , "outdir": "./reports/servers" , "servers": [{ "agent": "RatchetRFC/0.1.0" - , "url": "ws://localhost:9001" + , "url": "ws://host.docker.internal:9001" , "options": {"version": 18} }] , "cases": [ - "1.*", - "2.*", - "3.*", - "4.*", - "5.*", - "6.*", - "7.*", - "8.*", - "9.*", - "10.*", - "12.1.1", - "12.2.1", - "12.3.1", - "12.4.1", - "12.5.1", - "13.1.1", - "13.2.1", - "13.3.1", - "13.4.1", - "13.5.1", - "13.6.1", - "13.7.1" + "*" ] , "exclude-cases": [] , "exclude-agent-cases": {} diff --git a/tests/ab/fuzzingserver.json b/tests/ab/fuzzingserver.json index 9f921a4..3a59bab 100644 --- a/tests/ab/fuzzingserver.json +++ b/tests/ab/fuzzingserver.json @@ -5,28 +5,7 @@ } , "outdir": "./reports/clients" , "cases": [ - "1.*", - "2.*", - "3.*", - "4.*", - "5.*", - "6.*", - "7.*", - "8.*", - "9.*", - "10.*", - "12.1.1", - "12.2.1", - "12.3.1", - "12.4.1", - "12.5.1", - "13.1.1", - "13.2.1", - "13.3.1", - "13.4.1", - "13.5.1", - "13.6.1", - "13.7.1" + "*" ] , "exclude-cases": [] , "exclude-agent-cases": {} diff --git a/tests/ab/run_ab_tests.sh b/tests/ab/run_ab_tests.sh index 4ccbc8f..630e806 100644 --- a/tests/ab/run_ab_tests.sh +++ b/tests/ab/run_ab_tests.sh @@ -9,16 +9,43 @@ if [ $(phpenv version-name) = "hhvm" -o $(phpenv version-name) = "5.4" -o $(phpe fi fi -wstest -m fuzzingserver -s fuzzingserver$SKIP_DEFLATE.json & +docker run --rm \ + -d \ + -v ${PWD}:/config \ + -v ${PWD}/reports:/reports \ + -p 9001:9001 \ + --name fuzzingserver \ + crossbario/autobahn-testsuite wstest -m fuzzingserver -s /config/fuzzingserver$SKIP_DEFLATE.json sleep 5 -php clientRunner.php +if [ "$TRAVIS" != "true" ]; then + echo "Running tests vs Autobahn test client" + ###docker run -it --rm --name abpytest crossbario/autobahn-testsuite wstest --mode testeeclient -w ws://host.docker.internal:9001 +fi +php -d memory_limit=256M clientRunner.php + +docker ps -a + +docker logs fuzzingserver + +docker stop fuzzingserver + + sleep 2 -php startServer.php & +php -d memory_limit=256M startServer.php & sleep 3 -wstest -m fuzzingclient -s fuzzingclient$SKIP_DEFLATE.json -sleep 1 -kill $(ps aux | grep 'php startServer.php' | awk '{print $2}' | head -n 1) + +docker run --rm \ + -it \ + -v ${PWD}:/config \ + -v ${PWD}/reports:/reports \ + --name fuzzingclient \ + crossbario/autobahn-testsuite wstest -m fuzzingclient -s /config/fuzzingclient$SKIP_DEFLATE.json +sleep 1 + +# send the shutdown command to the PHP echo server +wget -O - -q http://127.0.0.1:9001/shutdown + diff --git a/tests/ab/startServer.php b/tests/ab/startServer.php index fa30394..6d5b46b 100644 --- a/tests/ab/startServer.php +++ b/tests/ab/startServer.php @@ -1,4 +1,6 @@ on('connection', function (React\Socket\ConnectionInterface $connection) use ($negotiator, $closeFrameChecker, $uException) { +$socket->on('connection', function (React\Socket\ConnectionInterface $connection) use ($negotiator, $closeFrameChecker, $uException, $socket) { $headerComplete = false; $buffer = ''; $parser = null; - $connection->on('data', function ($data) use ($connection, &$parser, &$headerComplete, &$buffer, $negotiator, $closeFrameChecker, $uException) { + $connection->on('data', function ($data) use ($connection, &$parser, &$headerComplete, &$buffer, $negotiator, $closeFrameChecker, $uException, $socket) { if ($headerComplete) { $parser->onData($data); return; @@ -38,6 +40,12 @@ $socket->on('connection', function (React\Socket\ConnectionInterface $connection $negotiatorResponse = $negotiatorResponse->withAddedHeader("Content-Length", "0"); + if ($negotiatorResponse->getStatusCode() !== 101 && $psrRequest->getUri()->getPath() === '/shutdown') { + $connection->end(\GuzzleHttp\Psr7\str(new Response(200, [], 'Shutting down echo server.'))); + $socket->close(); + return; + }; + $connection->write(\GuzzleHttp\Psr7\str($negotiatorResponse)); if ($negotiatorResponse->getStatusCode() !== 101) { From 5cdc4478f7c8f4f279181b7763fe18331a860d80 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Thu, 9 Apr 2020 17:49:52 -0400 Subject: [PATCH 31/40] Correct output path on fuzzingclient config --- tests/ab/fuzzingclient.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ab/fuzzingclient.json b/tests/ab/fuzzingclient.json index fc3869b..aa41362 100644 --- a/tests/ab/fuzzingclient.json +++ b/tests/ab/fuzzingclient.json @@ -2,7 +2,7 @@ "options": { "failByDrop": false } - , "outdir": "./reports/servers" + , "outdir": "/reports/servers" , "servers": [{ "agent": "RatchetRFC/0.1.0" , "url": "ws://host.docker.internal:9001" From 1a4fa37f0625e7f11c78820caeb6ee3a67f40b9c Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Thu, 9 Apr 2020 18:07:42 -0400 Subject: [PATCH 32/40] Fix fuzzingclient_skip_deflate host name --- tests/ab/fuzzingclient_skip_deflate.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ab/fuzzingclient_skip_deflate.json b/tests/ab/fuzzingclient_skip_deflate.json index 88732c9..3d7780c 100644 --- a/tests/ab/fuzzingclient_skip_deflate.json +++ b/tests/ab/fuzzingclient_skip_deflate.json @@ -2,10 +2,10 @@ "options": { "failByDrop": false } - , "outdir": "./reports/servers" + , "outdir": "/reports/servers" , "servers": [{ "agent": "RatchetRFC/0.1.0" - , "url": "ws://localhost:9001" + , "url": "ws://host.docker.internal:9001" , "options": {"version": 18} }] , "cases": ["*"] From 5c15dd0a2f1c44123527308850fab118e6ed789b Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Fri, 10 Apr 2020 13:15:49 -0400 Subject: [PATCH 33/40] Try to get IP address sorted out for travis --- tests/ab/clientRunner.php | 9 +++++++-- tests/ab/docker_bootstrap.sh | 12 ++++++++++++ tests/ab/fuzzingclient.json | 2 +- tests/ab/fuzzingclient_skip_deflate.json | 4 ++-- tests/ab/run_ab_tests.sh | 7 ++++++- tests/ab/startServer.php | 4 ++-- 6 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 tests/ab/docker_bootstrap.sh diff --git a/tests/ab/clientRunner.php b/tests/ab/clientRunner.php index d41a645..29aef92 100644 --- a/tests/ab/clientRunner.php +++ b/tests/ab/clientRunner.php @@ -246,8 +246,13 @@ getTestCases()->then(function ($count) use ($loop) { $allDeferred->resolve(); return; } - echo "Running test $i/$count\n"; - runTest($i)->then($runNextCase); + echo "Running test $i/$count..."; + $startTime = microtime(true); + runTest($i) + ->then(function () use ($startTime) { + echo " completed " . round((microtime(true) - $startTime) * 1000) . " ms\n"; + }) + ->then($runNextCase); }; $i = 0; diff --git a/tests/ab/docker_bootstrap.sh b/tests/ab/docker_bootstrap.sh new file mode 100644 index 0000000..44d4581 --- /dev/null +++ b/tests/ab/docker_bootstrap.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -x + +echo "Running $0" + +echo Adding "$1 host.ratchet.internal" to /etc/hosts file + +echo $1 host.ratchet.internal >> /etc/hosts + +echo /etc/hosts contains: +cat /etc/hosts +echo diff --git a/tests/ab/fuzzingclient.json b/tests/ab/fuzzingclient.json index aa41362..1148d12 100644 --- a/tests/ab/fuzzingclient.json +++ b/tests/ab/fuzzingclient.json @@ -5,7 +5,7 @@ , "outdir": "/reports/servers" , "servers": [{ "agent": "RatchetRFC/0.1.0" - , "url": "ws://host.docker.internal:9001" + , "url": "ws://host.ratchet.internal:9001" , "options": {"version": 18} }] , "cases": [ diff --git a/tests/ab/fuzzingclient_skip_deflate.json b/tests/ab/fuzzingclient_skip_deflate.json index 3d7780c..7a8fae7 100644 --- a/tests/ab/fuzzingclient_skip_deflate.json +++ b/tests/ab/fuzzingclient_skip_deflate.json @@ -5,10 +5,10 @@ , "outdir": "/reports/servers" , "servers": [{ "agent": "RatchetRFC/0.1.0" - , "url": "ws://host.docker.internal:9001" + , "url": "ws://host.ratchet.internal:9001" , "options": {"version": 18} }] - , "cases": ["*"] + , "cases": ["9"] , "exclude-cases": ["12.*", "13.*"] , "exclude-agent-cases": {} } diff --git a/tests/ab/run_ab_tests.sh b/tests/ab/run_ab_tests.sh index 630e806..cbde025 100644 --- a/tests/ab/run_ab_tests.sh +++ b/tests/ab/run_ab_tests.sh @@ -36,13 +36,18 @@ sleep 2 php -d memory_limit=256M startServer.php & sleep 3 +if [ "$OSTYPE" = "linux-gnu" ]; then + IPADDR=`hostname -I | cut -f 1 -d ' '` +else + IPADDR=`ifconfig | grep "inet " | grep -Fv 127.0.0.1 | awk '{print $2}' | head -1` +fi docker run --rm \ -it \ -v ${PWD}:/config \ -v ${PWD}/reports:/reports \ --name fuzzingclient \ - crossbario/autobahn-testsuite wstest -m fuzzingclient -s /config/fuzzingclient$SKIP_DEFLATE.json + crossbario/autobahn-testsuite /bin/sh -c "sh /config/docker_bootstrap.sh $IPADDR; wstest -m fuzzingclient -s /config/fuzzingclient$SKIP_DEFLATE.json" sleep 1 # send the shutdown command to the PHP echo server diff --git a/tests/ab/startServer.php b/tests/ab/startServer.php index 6d5b46b..f1d3b66 100644 --- a/tests/ab/startServer.php +++ b/tests/ab/startServer.php @@ -11,7 +11,7 @@ require_once __DIR__ . "/../bootstrap.php"; $loop = \React\EventLoop\Factory::create(); -$socket = new \React\Socket\Server('127.0.0.1:9001', $loop); +$socket = new \React\Socket\Server('0.0.0.0:9001', $loop); $closeFrameChecker = new \Ratchet\RFC6455\Messaging\CloseFrameChecker; $negotiator = new \Ratchet\RFC6455\Handshake\ServerNegotiator(new \Ratchet\RFC6455\Handshake\RequestVerifier, PermessageDeflateOptions::permessageDeflateSupported()); @@ -41,7 +41,7 @@ $socket->on('connection', function (React\Socket\ConnectionInterface $connection $negotiatorResponse = $negotiatorResponse->withAddedHeader("Content-Length", "0"); if ($negotiatorResponse->getStatusCode() !== 101 && $psrRequest->getUri()->getPath() === '/shutdown') { - $connection->end(\GuzzleHttp\Psr7\str(new Response(200, [], 'Shutting down echo server.'))); + $connection->end(\GuzzleHttp\Psr7\str(new Response(200, [], 'Shutting down echo server.' . PHP_EOL))); $socket->close(); return; }; From 14fdcbb84e29ddffccd400a961c25f3a319371fd Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Fri, 10 Apr 2020 14:03:21 -0400 Subject: [PATCH 34/40] More travis adjustments --- tests/ab/fuzzingclient_skip_deflate.json | 2 +- tests/ab/run_ab_tests.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ab/fuzzingclient_skip_deflate.json b/tests/ab/fuzzingclient_skip_deflate.json index 7a8fae7..229762c 100644 --- a/tests/ab/fuzzingclient_skip_deflate.json +++ b/tests/ab/fuzzingclient_skip_deflate.json @@ -8,7 +8,7 @@ , "url": "ws://host.ratchet.internal:9001" , "options": {"version": 18} }] - , "cases": ["9"] + , "cases": ["*"] , "exclude-cases": ["12.*", "13.*"] , "exclude-agent-cases": {} } diff --git a/tests/ab/run_ab_tests.sh b/tests/ab/run_ab_tests.sh index cbde025..4b46c87 100644 --- a/tests/ab/run_ab_tests.sh +++ b/tests/ab/run_ab_tests.sh @@ -39,7 +39,7 @@ sleep 3 if [ "$OSTYPE" = "linux-gnu" ]; then IPADDR=`hostname -I | cut -f 1 -d ' '` else - IPADDR=`ifconfig | grep "inet " | grep -Fv 127.0.0.1 | awk '{print $2}' | head -1` + IPADDR=`ifconfig | grep "inet " | grep -Fv 127.0.0.1 | awk '{print $2}' | head -1 | tr -d 'adr:'` fi docker run --rm \ From cdb37b17a4114371fc0191265bdd56ea9d631260 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Sat, 11 Apr 2020 19:31:44 -0400 Subject: [PATCH 35/40] Separate travis server and client tests --- .travis.yml | 4 + tests/AbResultsTest.php | 6 +- tests/ab/run_ab_tests.sh | 74 ++++++++++--------- .../PermessageDeflateOptionsTest.php | 3 +- tests/unit/Handshake/RequestVerifierTest.php | 7 +- tests/unit/Handshake/ResponseVerifierTest.php | 7 +- tests/unit/Handshake/ServerNegotiatorTest.php | 3 +- tests/unit/Messaging/FrameTest.php | 5 +- tests/unit/Messaging/MessageBufferTest.php | 3 +- tests/unit/Messaging/MessageTest.php | 5 +- 10 files changed, 70 insertions(+), 47 deletions(-) diff --git a/.travis.yml b/.travis.yml index a7be1be..f8f7b7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,10 @@ php: - 7.4 - nightly +env: + - ABTEST=client + - ABTEST=server + matrix: allow_failures: - php: nightly diff --git a/tests/AbResultsTest.php b/tests/AbResultsTest.php index 9bc502d..9bd799e 100644 --- a/tests/AbResultsTest.php +++ b/tests/AbResultsTest.php @@ -1,7 +1,9 @@ markTestSkipped('Autobahn TestSuite results not found'); diff --git a/tests/ab/run_ab_tests.sh b/tests/ab/run_ab_tests.sh index 4b46c87..b924480 100644 --- a/tests/ab/run_ab_tests.sh +++ b/tests/ab/run_ab_tests.sh @@ -9,48 +9,50 @@ if [ $(phpenv version-name) = "hhvm" -o $(phpenv version-name) = "5.4" -o $(phpe fi fi -docker run --rm \ - -d \ - -v ${PWD}:/config \ - -v ${PWD}/reports:/reports \ - -p 9001:9001 \ - --name fuzzingserver \ - crossbario/autobahn-testsuite wstest -m fuzzingserver -s /config/fuzzingserver$SKIP_DEFLATE.json -sleep 5 -if [ "$TRAVIS" != "true" ]; then - echo "Running tests vs Autobahn test client" - ###docker run -it --rm --name abpytest crossbario/autobahn-testsuite wstest --mode testeeclient -w ws://host.docker.internal:9001 -fi -php -d memory_limit=256M clientRunner.php +if [ "$ABTEST" = "client" ]; then + docker run --rm \ + -d \ + -v ${PWD}:/config \ + -v ${PWD}/reports:/reports \ + -p 9001:9001 \ + --name fuzzingserver \ + crossbario/autobahn-testsuite wstest -m fuzzingserver -s /config/fuzzingserver$SKIP_DEFLATE.json + sleep 5 + if [ "$TRAVIS" != "true" ]; then + echo "Running tests vs Autobahn test client" + ###docker run -it --rm --name abpytest crossbario/autobahn-testsuite wstest --mode testeeclient -w ws://host.docker.internal:9001 + fi + php -d memory_limit=256M clientRunner.php -docker ps -a + docker ps -a -docker logs fuzzingserver + docker logs fuzzingserver -docker stop fuzzingserver + docker stop fuzzingserver - - -sleep 2 - -php -d memory_limit=256M startServer.php & -sleep 3 - -if [ "$OSTYPE" = "linux-gnu" ]; then - IPADDR=`hostname -I | cut -f 1 -d ' '` -else - IPADDR=`ifconfig | grep "inet " | grep -Fv 127.0.0.1 | awk '{print $2}' | head -1 | tr -d 'adr:'` + sleep 2 fi -docker run --rm \ - -it \ - -v ${PWD}:/config \ - -v ${PWD}/reports:/reports \ - --name fuzzingclient \ - crossbario/autobahn-testsuite /bin/sh -c "sh /config/docker_bootstrap.sh $IPADDR; wstest -m fuzzingclient -s /config/fuzzingclient$SKIP_DEFLATE.json" -sleep 1 +if [ "$ABTEST" = "server" ]; then + php -d memory_limit=256M startServer.php & + sleep 3 -# send the shutdown command to the PHP echo server -wget -O - -q http://127.0.0.1:9001/shutdown + if [ "$OSTYPE" = "linux-gnu" ]; then + IPADDR=`hostname -I | cut -f 1 -d ' '` + else + IPADDR=`ifconfig | grep "inet " | grep -Fv 127.0.0.1 | awk '{print $2}' | head -1 | tr -d 'adr:'` + fi + + docker run --rm \ + -it \ + -v ${PWD}:/config \ + -v ${PWD}/reports:/reports \ + --name fuzzingclient \ + crossbario/autobahn-testsuite /bin/sh -c "sh /config/docker_bootstrap.sh $IPADDR; wstest -m fuzzingclient -s /config/fuzzingclient$SKIP_DEFLATE.json" + sleep 1 + + # send the shutdown command to the PHP echo server + wget -O - -q http://127.0.0.1:9001/shutdown +fi diff --git a/tests/unit/Handshake/PermessageDeflateOptionsTest.php b/tests/unit/Handshake/PermessageDeflateOptionsTest.php index 4b5fbe1..11d3739 100644 --- a/tests/unit/Handshake/PermessageDeflateOptionsTest.php +++ b/tests/unit/Handshake/PermessageDeflateOptionsTest.php @@ -3,8 +3,9 @@ namespace Ratchet\RFC6455\Test\Unit\Handshake; use Ratchet\RFC6455\Handshake\PermessageDeflateOptions; +use PHPUnit\Framework\TestCase; -class PermessageDeflateOptionsTest extends \PHPUnit_Framework_TestCase +class PermessageDeflateOptionsTest extends TestCase { public static function versionSupportProvider() { return [ diff --git a/tests/unit/Handshake/RequestVerifierTest.php b/tests/unit/Handshake/RequestVerifierTest.php index 239de33..e6dea0e 100644 --- a/tests/unit/Handshake/RequestVerifierTest.php +++ b/tests/unit/Handshake/RequestVerifierTest.php @@ -1,17 +1,20 @@ _v = new RequestVerifier(); } diff --git a/tests/unit/Handshake/ResponseVerifierTest.php b/tests/unit/Handshake/ResponseVerifierTest.php index 312930e..b916005 100644 --- a/tests/unit/Handshake/ResponseVerifierTest.php +++ b/tests/unit/Handshake/ResponseVerifierTest.php @@ -1,17 +1,20 @@ _v = new ResponseVerifier; } diff --git a/tests/unit/Handshake/ServerNegotiatorTest.php b/tests/unit/Handshake/ServerNegotiatorTest.php index 6fa8e64..c08b9f3 100644 --- a/tests/unit/Handshake/ServerNegotiatorTest.php +++ b/tests/unit/Handshake/ServerNegotiatorTest.php @@ -4,8 +4,9 @@ namespace Ratchet\RFC6455\Test\Unit\Handshake; use Ratchet\RFC6455\Handshake\RequestVerifier; use Ratchet\RFC6455\Handshake\ServerNegotiator; +use PHPUnit\Framework\TestCase; -class ServerNegotiatorTest extends \PHPUnit_Framework_TestCase +class ServerNegotiatorTest extends TestCase { public function testNoUpgradeRequested() { $negotiator = new ServerNegotiator(new RequestVerifier()); diff --git a/tests/unit/Messaging/FrameTest.php b/tests/unit/Messaging/FrameTest.php index b73f600..54a4599 100644 --- a/tests/unit/Messaging/FrameTest.php +++ b/tests/unit/Messaging/FrameTest.php @@ -1,13 +1,16 @@ Date: Sat, 11 Apr 2020 22:22:25 -0400 Subject: [PATCH 36/40] Remove return types to keep backward compatibility --- tests/unit/Handshake/RequestVerifierTest.php | 2 +- tests/unit/Handshake/ResponseVerifierTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/Handshake/RequestVerifierTest.php b/tests/unit/Handshake/RequestVerifierTest.php index e6dea0e..5ba26b6 100644 --- a/tests/unit/Handshake/RequestVerifierTest.php +++ b/tests/unit/Handshake/RequestVerifierTest.php @@ -14,7 +14,7 @@ class RequestVerifierTest extends TestCase { */ protected $_v; - public function setUp() : void { + public function setUp() { $this->_v = new RequestVerifier(); } diff --git a/tests/unit/Handshake/ResponseVerifierTest.php b/tests/unit/Handshake/ResponseVerifierTest.php index b916005..0ca18e3 100644 --- a/tests/unit/Handshake/ResponseVerifierTest.php +++ b/tests/unit/Handshake/ResponseVerifierTest.php @@ -14,7 +14,7 @@ class ResponseVerifierTest extends TestCase { */ protected $_v; - public function setUp() : void { + public function setUp() { $this->_v = new ResponseVerifier; } From 5911d8bc3559a6ac32ad750a37820f36ed81afb9 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Thu, 7 May 2020 10:46:40 -0400 Subject: [PATCH 37/40] Bump test agent version --- tests/ab/clientRunner.php | 2 +- tests/ab/fuzzingclient.json | 2 +- tests/ab/fuzzingclient_skip_deflate.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ab/clientRunner.php b/tests/ab/clientRunner.php index 29aef92..8dd964b 100644 --- a/tests/ab/clientRunner.php +++ b/tests/ab/clientRunner.php @@ -13,7 +13,7 @@ use React\Socket\Connector; require __DIR__ . '/../bootstrap.php'; -define('AGENT', 'RatchetRFC/0.0.0'); +define('AGENT', 'RatchetRFC/0.3'); $testServer = "127.0.0.1"; diff --git a/tests/ab/fuzzingclient.json b/tests/ab/fuzzingclient.json index 1148d12..d410be3 100644 --- a/tests/ab/fuzzingclient.json +++ b/tests/ab/fuzzingclient.json @@ -4,7 +4,7 @@ } , "outdir": "/reports/servers" , "servers": [{ - "agent": "RatchetRFC/0.1.0" + "agent": "RatchetRFC/0.3" , "url": "ws://host.ratchet.internal:9001" , "options": {"version": 18} }] diff --git a/tests/ab/fuzzingclient_skip_deflate.json b/tests/ab/fuzzingclient_skip_deflate.json index 229762c..b1fddbe 100644 --- a/tests/ab/fuzzingclient_skip_deflate.json +++ b/tests/ab/fuzzingclient_skip_deflate.json @@ -4,7 +4,7 @@ } , "outdir": "/reports/servers" , "servers": [{ - "agent": "RatchetRFC/0.1.0" + "agent": "RatchetRFC/0.3" , "url": "ws://host.ratchet.internal:9001" , "options": {"version": 18} }] From 52b27fd7b4ffbd7d0cd4de55c13ec4df044c05a3 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Thu, 7 May 2020 10:52:36 -0400 Subject: [PATCH 38/40] Use exception factory that is passed in --- src/Messaging/MessageBuffer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Messaging/MessageBuffer.php b/src/Messaging/MessageBuffer.php index e6bf1ce..d761786 100644 --- a/src/Messaging/MessageBuffer.php +++ b/src/Messaging/MessageBuffer.php @@ -93,7 +93,7 @@ class MessageBuffer { $this->closeFrameChecker = $frameChecker; $this->checkForMask = (bool)$expectMask; - $this->exceptionFactory ?: $this->exceptionFactory = function($msg) { + $this->exceptionFactory ?: $exceptionFactory = function($msg) { return new \UnderflowException($msg); }; From 11f98fc6ff979afb0865ce75622094711b92187e Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 7 May 2020 10:57:39 -0400 Subject: [PATCH 39/40] Updating composer --- composer.json | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index c7f22c7..0416b58 100644 --- a/composer.json +++ b/composer.json @@ -5,15 +5,20 @@ "keywords": ["WebSockets", "websocket", "RFC6455"], "homepage": "http://socketo.me", "license": "MIT", - "authors": [{ - "name": "Chris Boden" - , "email": "cboden@gmail.com" - , "role": "Developer" - }], + "authors": [ + { + "name": "Chris Boden" + , "email": "cboden@gmail.com" + , "role": "Developer" + }, + { + "name": "Matt Bonneau", + "role": "Developer" + } + ], "support": { - "forum": "https://groups.google.com/forum/#!forum/ratchet-php" - , "issues": "https://github.com/ratchetphp/RFC6455/issues" - , "irc": "irc://irc.freenode.org/reactphp" + "issues": "https://github.com/ratchetphp/RFC6455/issues", + "chat": "https://gitter.im/reactphp/reactphp" }, "autoload": { "psr-4": { @@ -29,10 +34,12 @@ "react/socket": "^1.3" }, "scripts": { - "abtests": "sh tests/ab/run_ab_tests.sh", + "abtest-client": "ABTEST=client && sh tests/ab/run_ab_tests.sh", + "abtest-server": "ABTEST=server && sh tests/ab/run_ab_tests.sh", "phpunit": "phpunit --colors=always", "test": [ - "@abtests", + "@abtest-client", + "@abtest-server", "@phpunit" ] } From acddde01786258a9f1df11f899d4a38878220bdd Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 7 May 2020 11:03:45 -0400 Subject: [PATCH 40/40] Link to AB report on socketo.me --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f17c14..fba5093 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # RFC6455 - The WebSocket Protocol [![Build Status](https://travis-ci.org/ratchetphp/RFC6455.svg?branch=master)](https://travis-ci.org/ratchetphp/RFC6455) -![Autobahn Testsuite](https://img.shields.io/badge/Autobahn-passing-brightgreen.svg) +[![Autobahn Testsuite](https://img.shields.io/badge/Autobahn-passing-brightgreen.svg)](http://socketo.me/reports/rfc-server/index.html) This library a protocol handler for the RFC6455 specification. It contains components for both server and client side handshake and messaging protocol negotation.