Commit inicial con archivos existentes

This commit is contained in:
2026-01-17 16:14:00 -06:00
parent 48671dc88e
commit 4c48c279de
2539 changed files with 2412708 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
<?php
namespace Ratchet\RFC6455\Test;
use PHPUnit\Framework\TestCase;
/**
* @coversNothing
*/
class AbResultsTest extends TestCase {
private function verifyAutobahnResults(string $fileName): void {
if (!file_exists($fileName)) {
$this->markTestSkipped('Autobahn TestSuite results not found');
}
$resultsJson = file_get_contents($fileName);
$results = json_decode($resultsJson);
$agentName = array_keys(get_object_vars($results))[0];
foreach ($results->$agentName as $name => $result) {
if ($result->behavior === "INFORMATIONAL") {
continue;
}
$this->assertTrue(in_array($result->behavior, ["OK", "NON-STRICT"]), "Autobahn test case " . $name . " in " . $fileName);
}
}
public function testAutobahnClientResults(): void {
$this->verifyAutobahnResults(__DIR__ . '/ab/reports/clients/index.json');
}
public function testAutobahnServerResults(): void {
$this->verifyAutobahnResults(__DIR__ . '/ab/reports/servers/index.json');
}
}

View File

@@ -0,0 +1,271 @@
<?php
use GuzzleHttp\Psr7\Message;
use GuzzleHttp\Psr7\Uri;
use Ratchet\RFC6455\Handshake\InvalidPermessageDeflateOptionsException;
use Ratchet\RFC6455\Handshake\PermessageDeflateOptions;
use Ratchet\RFC6455\Messaging\FrameInterface;
use Ratchet\RFC6455\Messaging\MessageBuffer;
use Ratchet\RFC6455\Handshake\ClientNegotiator;
use Ratchet\RFC6455\Messaging\CloseFrameChecker;
use Ratchet\RFC6455\Messaging\MessageInterface;
use React\Promise\Deferred;
use Ratchet\RFC6455\Messaging\Frame;
use React\Promise\PromiseInterface;
use GuzzleHttp\Psr7\HttpFactory;
use React\Socket\ConnectionInterface;
use React\Socket\Connector;
require __DIR__ . '/../bootstrap.php';
define('AGENT', 'RatchetRFC/0.4');
$testServer = $argc > 1 ? $argv[1] : "127.0.0.1";
$loop = React\EventLoop\Factory::create();
$connector = new Connector($loop);
function echoStreamerFactory(ConnectionInterface $conn, ?PermessageDeflateOptions $permessageDeflateOptions = null): MessageBuffer
{
$permessageDeflateOptions = $permessageDeflateOptions ?: PermessageDeflateOptions::createDisabled();
return new MessageBuffer(
new CloseFrameChecker,
static function (MessageInterface $msg, MessageBuffer $messageBuffer) use ($conn): void {
$messageBuffer->sendMessage($msg->getPayload(), true, $msg->isBinary());
},
static function (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());
case Frame::OP_CLOSE:
return $conn->end((new Frame($frame->getPayload(), true, Frame::OP_CLOSE))->maskPayload()->getContents());
}
},
false,
null,
null,
null,
[$conn, 'write'],
$permessageDeflateOptions
);
}
function getTestCases(): PromiseInterface {
global $testServer;
global $connector;
$deferred = new Deferred();
$connector->connect($testServer . ':9002')->then(static function (ConnectionInterface $connection) use ($deferred, $testServer): void {
$cn = new ClientNegotiator(new HttpFactory());
$cnRequest = $cn->generateRequest(new Uri('ws://' . $testServer . ':9002/getCaseCount'));
$rawResponse = "";
$response = null;
/** @var MessageBuffer $ms */
$ms = null;
$connection->on('data', static function ($data) use ($connection, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest): void {
if ($response === null) {
$rawResponse .= $data;
$pos = strpos($rawResponse, "\r\n\r\n");
if ($pos) {
$data = substr($rawResponse, $pos + 4);
$rawResponse = substr($rawResponse, 0, $pos + 4);
$response = Message::parseResponse($rawResponse);
if (!$cn->validateResponse($cnRequest, $response)) {
$connection->end();
$deferred->reject();
} else {
$ms = new MessageBuffer(
new CloseFrameChecker,
static function (MessageInterface $msg) use ($deferred, $connection): void {
$deferred->resolve($msg->getPayload());
$connection->close();
},
null,
false,
null,
null,
null,
static function (): void {}
);
}
}
}
// feed the message streamer
if ($ms) {
$ms->onData($data);
}
});
$connection->write(Message::toString($cnRequest));
});
return $deferred->promise();
}
$cn = new ClientNegotiator(
new HttpFactory(),
PermessageDeflateOptions::permessageDeflateSupported() ? PermessageDeflateOptions::createEnabled() : null);
function runTest(int $case)
{
global $connector;
global $testServer;
global $cn;
$casePath = "/runCase?case={$case}&agent=" . AGENT;
$deferred = new Deferred();
$connector->connect($testServer . ':9002')->then(static function (ConnectionInterface $connection) use ($deferred, $casePath, $case, $testServer): void {
$cn = new ClientNegotiator(
new HttpFactory(),
PermessageDeflateOptions::permessageDeflateSupported() ? PermessageDeflateOptions::createEnabled() : null);
$cnRequest = $cn->generateRequest(new Uri('ws://' . $testServer . ':9002' . $casePath));
$rawResponse = "";
$response = null;
$ms = null;
$connection->on('data', static function ($data) use ($connection, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest): void {
if ($response === null) {
$rawResponse .= $data;
$pos = strpos($rawResponse, "\r\n\r\n");
if ($pos) {
$data = substr($rawResponse, $pos + 4);
$rawResponse = substr($rawResponse, 0, $pos + 4);
$response = Message::parseResponse($rawResponse);
if (!$cn->validateResponse($cnRequest, $response)) {
echo "Invalid response.\n";
$connection->end();
$deferred->reject();
} else {
try {
$permessageDeflateOptions = PermessageDeflateOptions::fromRequestOrResponse($response)[0];
$ms = echoStreamerFactory(
$connection,
$permessageDeflateOptions
);
} catch (InvalidPermessageDeflateOptionsException $e) {
$connection->end();
}
}
}
}
// feed the message streamer
if ($ms) {
$ms->onData($data);
}
});
$connection->on('close', static function () use ($deferred): void {
$deferred->resolve(null);
});
$connection->write(Message::toString($cnRequest));
});
return $deferred->promise();
}
function createReport(): PromiseInterface {
global $connector;
global $testServer;
$deferred = new Deferred();
$connector->connect($testServer . ':9002')->then(static function (ConnectionInterface $connection) use ($deferred, $testServer): void {
// $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(new HttpFactory());
$cnRequest = $cn->generateRequest(new Uri('ws://' . $testServer . ':9002' . $reportPath));
$rawResponse = "";
$response = null;
/** @var MessageBuffer $ms */
$ms = null;
$connection->on('data', static function ($data) use ($connection, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest): void {
if ($response === null) {
$rawResponse .= $data;
$pos = strpos($rawResponse, "\r\n\r\n");
if ($pos) {
$data = substr($rawResponse, $pos + 4);
$rawResponse = substr($rawResponse, 0, $pos + 4);
$response = Message::parseResponse($rawResponse);
if (!$cn->validateResponse($cnRequest, $response)) {
$connection->end();
$deferred->reject();
} else {
$ms = new MessageBuffer(
new CloseFrameChecker,
static function (MessageInterface $msg) use ($deferred, $connection): void {
$deferred->resolve($msg->getPayload());
$connection->close();
},
null,
false,
null,
null,
null,
static function (): void {}
);
}
}
}
// feed the message streamer
if ($ms) {
$ms->onData($data);
}
});
$connection->write(Message::toString($cnRequest));
});
return $deferred->promise();
}
$testPromises = [];
getTestCases()->then(function ($count) use ($loop) {
$allDeferred = new Deferred();
$runNextCase = static function () use (&$i, &$runNextCase, $count, $allDeferred): void {
$i++;
if ($i > $count) {
$allDeferred->resolve(null);
return;
}
echo "Running test $i/$count...";
$startTime = microtime(true);
runTest($i)
->then(static function () use ($startTime): void {
echo " completed " . round((microtime(true) - $startTime) * 1000) . " ms\n";
})
->then($runNextCase);
};
$i = 0;
$runNextCase();
$allDeferred->promise()->then(static function (): void {
createReport();
});
});
$loop->run();

View File

@@ -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

View File

@@ -0,0 +1,16 @@
{
"options": {
"failByDrop": false
}
, "outdir": "/reports/servers"
, "servers": [{
"agent": "RatchetRFC/0.4"
, "url": "ws://host.ratchet.internal:9001"
, "options": {"version": 18}
}]
, "cases": [
"*"
]
, "exclude-cases": []
, "exclude-agent-cases": {}
}

View File

@@ -0,0 +1,14 @@
{
"options": {
"failByDrop": false
}
, "outdir": "/reports/servers"
, "servers": [{
"agent": "RatchetRFC/0.4"
, "url": "ws://host.ratchet.internal:9001"
, "options": {"version": 18}
}]
, "cases": ["*"]
, "exclude-cases": ["12.*", "13.*"]
, "exclude-agent-cases": {}
}

View File

@@ -0,0 +1,12 @@
{
"url": "ws://127.0.0.1:9002"
, "options": {
"failByDrop": false
}
, "outdir": "./reports/clients"
, "cases": [
"*"
]
, "exclude-cases": []
, "exclude-agent-cases": {}
}

View File

@@ -0,0 +1,10 @@
{
"url": "ws://127.0.0.1:9002"
, "options": {
"failByDrop": false
}
, "outdir": "./reports/clients"
, "cases": ["*"]
, "exclude-cases": ["12.*", "13.*"]
, "exclude-agent-cases": {}
}

View File

@@ -0,0 +1,48 @@
set -x
cd tests/ab
if [ "$ABTEST" = "client" ]; then
docker run --rm \
-d \
-v ${PWD}:/config \
-v ${PWD}/reports:/reports \
-p 9002:9002 \
--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:9002
fi
php -d memory_limit=256M clientRunner.php
docker ps -a
docker logs fuzzingserver
docker stop fuzzingserver
sleep 2
fi
if [ "$ABTEST" = "server" ]; then
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:'`
fi
docker run --rm \
-i \
-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

View File

@@ -0,0 +1,93 @@
<?php
use GuzzleHttp\Psr7\Message;
use GuzzleHttp\Psr7\Response;
use Ratchet\RFC6455\Handshake\PermessageDeflateOptions;
use Ratchet\RFC6455\Handshake\RequestVerifier;
use Ratchet\RFC6455\Handshake\ServerNegotiator;
use Ratchet\RFC6455\Messaging\CloseFrameChecker;
use Ratchet\RFC6455\Messaging\MessageBuffer;
use Ratchet\RFC6455\Messaging\MessageInterface;
use Ratchet\RFC6455\Messaging\FrameInterface;
use Ratchet\RFC6455\Messaging\Frame;
use GuzzleHttp\Psr7\HttpFactory;
require_once __DIR__ . "/../bootstrap.php";
$loop = \React\EventLoop\Factory::create();
$socket = new \React\Socket\Server('0.0.0.0:9001', $loop);
$closeFrameChecker = new CloseFrameChecker;
$negotiator = new ServerNegotiator(
new RequestVerifier,
new HttpFactory(),
PermessageDeflateOptions::permessageDeflateSupported()
);
$uException = new \UnderflowException;
$socket->on('connection', static function (React\Socket\ConnectionInterface $connection) use ($negotiator, $closeFrameChecker, $uException, $socket): void {
$headerComplete = false;
$buffer = '';
$parser = null;
$connection->on('data', static function ($data) use ($connection, &$parser, &$headerComplete, &$buffer, $negotiator, $closeFrameChecker, $uException, $socket): void {
if ($headerComplete) {
$parser->onData($data);
return;
}
$buffer .= $data;
$parts = explode("\r\n\r\n", $buffer);
if (count($parts) < 2) {
return;
}
$headerComplete = true;
$psrRequest = Message::parseRequest($parts[0] . "\r\n\r\n");
$negotiatorResponse = $negotiator->handshake($psrRequest);
$negotiatorResponse = $negotiatorResponse->withAddedHeader("Content-Length", "0");
if ($negotiatorResponse->getStatusCode() !== 101 && $psrRequest->getUri()->getPath() === '/shutdown') {
$connection->end(Message::toString(new Response(200, [], 'Shutting down echo server.' . PHP_EOL)));
$socket->close();
return;
};
$connection->write(Message::toString($negotiatorResponse));
if ($negotiatorResponse->getStatusCode() !== 101) {
$connection->end();
return;
}
// there is no need to look through the client requests
// we support any valid permessage deflate
$deflateOptions = PermessageDeflateOptions::fromRequestOrResponse($psrRequest)[0];
$parser = new MessageBuffer($closeFrameChecker,
static function (MessageInterface $message, MessageBuffer $messageBuffer): void {
$messageBuffer->sendMessage($message->getPayload(), true, $message->isBinary());
}, static function (FrameInterface $frame) use ($connection, &$parser): void {
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, static fn (): \Exception => $uException,
null,
null,
[$connection, 'write'],
$deflateOptions);
array_shift($parts);
$parser->onData(implode("\r\n\r\n", $parts));
});
});
$loop->run();

19
vendor/ratchet/rfc6455/tests/bootstrap.php vendored Executable file
View File

@@ -0,0 +1,19 @@
<?php
/**
* Find the auto loader file
*/
$files = [
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../vendor/autoload.php',
__DIR__ . '/../../../vendor/autoload.php',
__DIR__ . '/../../../../vendor/autoload.php',
];
foreach ($files as $file) {
if (file_exists($file)) {
$loader = require $file;
$loader->addPsr4('Ratchet\\RFC6455\\Test\\', __DIR__);
break;
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Ratchet\RFC6455\Test\Unit\Handshake;
use Ratchet\RFC6455\Handshake\PermessageDeflateOptions;
use PHPUnit\Framework\TestCase;
/**
* @covers Ratchet\RFC6455\Handshake\PermessageDeflateOptions
*/
class PermessageDeflateOptionsTest extends TestCase
{
public static function versionSupportProvider(): array {
return [
['7.0.17', false],
['7.0.18', true],
['7.0.200', true],
['5.6.0', false],
['7.1.3', false],
['7.1.4', true],
['7.1.200', true],
['10.0.0', true]
];
}
/**
* @requires function deflate_init
* @dataProvider versionSupportProvider
*/
public function testVersionSupport(string $version, bool $supported): void {
$this->assertEquals($supported, PermessageDeflateOptions::permessageDeflateSupported($version));
}
}

View File

@@ -0,0 +1,180 @@
<?php
namespace Ratchet\RFC6455\Test\Unit\Handshake;
use Ratchet\RFC6455\Handshake\RequestVerifier;
use PHPUnit\Framework\TestCase;
/**
* @covers Ratchet\RFC6455\Handshake\RequestVerifier
*/
class RequestVerifierTest extends TestCase {
/**
* @var RequestVerifier
*/
protected $_v;
protected function setUp(): void {
$this->_v = new RequestVerifier();
}
public static function methodProvider(): array {
return array(
array(true, 'GET'),
array(true, 'get'),
array(true, 'Get'),
array(false, 'POST'),
array(false, 'DELETE'),
array(false, 'PUT'),
array(false, 'PATCH')
);
}
/**
* @dataProvider methodProvider
*/
public function testMethodMustBeGet(bool $result, string $in): void {
$this->assertEquals($result, $this->_v->verifyMethod($in));
}
public static function httpVersionProvider(): array {
return array(
array(true, 1.1),
array(true, '1.1'),
array(true, 1.2),
array(true, '1.2'),
array(true, 2),
array(true, '2'),
array(true, '2.0'),
array(false, '1.0'),
array(false, 1),
array(false, '0.9'),
array(false, ''),
array(false, 'hello')
);
}
/**
* @dataProvider httpVersionProvider
*/
public function testHttpVersionIsAtLeast1Point1(bool $expected, $in): void {
$this->assertEquals($expected, $this->_v->verifyHTTPVersion($in));
}
public static function uRIProvider(): array {
return array(
array(true, '/chat'),
array(true, '/hello/world?key=val'),
array(false, '/chat#bad'),
array(false, 'nope'),
array(false, '/ ಠ_ಠ '),
array(false, '/✖')
);
}
/**
* @dataProvider URIProvider
*/
public function testRequestUri(bool $expected, string $in): void {
$this->assertEquals($expected, $this->_v->verifyRequestURI($in));
}
public static function hostProvider(): array {
return array(
array(true, ['server.example.com']),
array(false, [])
);
}
/**
* @dataProvider HostProvider
*/
public function testVerifyHostIsSet(bool $expected, array $in): void {
$this->assertEquals($expected, $this->_v->verifyHost($in));
}
public static function upgradeProvider(): array {
return array(
array(true, ['websocket']),
array(true, ['Websocket']),
array(true, ['webSocket']),
array(false, []),
array(false, [''])
);
}
/**
* @dataProvider upgradeProvider
*/
public function testVerifyUpgradeIsWebSocket(bool $expected, array $val): void {
$this->assertEquals($expected, $this->_v->verifyUpgradeRequest($val));
}
public static function connectionProvider(): array {
return array(
array(true, ['Upgrade']),
array(true, ['upgrade']),
array(true, ['keep-alive', 'Upgrade']),
array(true, ['Upgrade', 'keep-alive']),
array(true, ['keep-alive', 'Upgrade', 'something']),
// as seen in Firefox 47.0.1 - see https://github.com/ratchetphp/RFC6455/issues/14
array(true, ['keep-alive, Upgrade']),
array(true, ['Upgrade, keep-alive']),
array(true, ['keep-alive, Upgrade, something']),
array(true, ['keep-alive, Upgrade', 'something']),
array(false, ['']),
array(false, [])
);
}
/**
* @dataProvider connectionProvider
*/
public function testConnectionHeaderVerification(bool $expected, array $val): void {
$this->assertEquals($expected, $this->_v->verifyConnection($val));
}
public static function keyProvider(): array {
return array(
array(true, ['hkfa1L7uwN6DCo4IS3iWAw==']),
array(true, ['765vVoQpKSGJwPzJIMM2GA==']),
array(true, ['AQIDBAUGBwgJCgsMDQ4PEC==']),
array(true, ['axa2B/Yz2CdpfQAY2Q5P7w==']),
array(false, [0]),
array(false, ['Hello World']),
array(false, ['1234567890123456']),
array(false, ['123456789012345678901234']),
array(true, [base64_encode('UTF8allthngs+✓')]),
array(true, ['dGhlIHNhbXBsZSBub25jZQ==']),
array(false, []),
array(false, ['dGhlIHNhbXBsZSBub25jZQ==', 'Some other value']),
array(false, ['Some other value', 'dGhlIHNhbXBsZSBub25jZQ=='])
);
}
/**
* @dataProvider keyProvider
*/
public function testKeyIsBase64Encoded16BitNonce(bool $expected, array $val): void {
$this->assertEquals($expected, $this->_v->verifyKey($val));
}
public static function versionProvider(): array {
return array(
array(true, [13]),
array(true, ['13']),
array(false, [12]),
array(false, [14]),
array(false, ['14']),
array(false, ['hi']),
array(false, ['']),
array(false, [])
);
}
/**
* @dataProvider versionProvider
*/
public function testVersionEquals13(bool $expected, array $in): void {
$this->assertEquals($expected, $this->_v->verifyVersion($in));
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Ratchet\RFC6455\Test\Unit\Handshake;
use Ratchet\RFC6455\Handshake\ResponseVerifier;
use PHPUnit\Framework\TestCase;
/**
* @covers Ratchet\RFC6455\Handshake\ResponseVerifier
*/
class ResponseVerifierTest extends TestCase {
/**
* @var ResponseVerifier
*/
protected $_v;
protected function setUp(): void {
$this->_v = new ResponseVerifier;
}
public static function subProtocolsProvider(): array {
return [
[true, ['a'], ['a']]
, [true, ['c', 'd', 'a'], ['a']]
, [true, ['c, a', 'd'], ['a']]
, [true, [], []]
, [true, ['a', 'b'], []]
, [false, ['c', 'd', 'a'], ['b', 'a']]
, [false, ['a', 'b', 'c'], ['d']]
];
}
/**
* @dataProvider subProtocolsProvider
*/
public function testVerifySubProtocol(bool $expected, array $request, array $response): void {
$this->assertEquals($expected, $this->_v->verifySubProtocol($request, $response));
}
}

View File

@@ -0,0 +1,223 @@
<?php
namespace Ratchet\RFC6455\Test\Unit\Handshake;
use GuzzleHttp\Psr7\Message;
use GuzzleHttp\Psr7\HttpFactory;
use Ratchet\RFC6455\Handshake\RequestVerifier;
use Ratchet\RFC6455\Handshake\ServerNegotiator;
use PHPUnit\Framework\TestCase;
/**
* @covers Ratchet\RFC6455\Handshake\ServerNegotiator
*/
class ServerNegotiatorTest extends TestCase
{
public function testNoUpgradeRequested(): void {
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());
$requestText = 'GET / HTTP/1.1
Host: 127.0.0.1:6789
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8
';
$request = Message::parseRequest($requestText);
$response = $negotiator->handshake($request);
$this->assertEquals('1.1', $response->getProtocolVersion());
$this->assertEquals(426, $response->getStatusCode());
$this->assertEquals('Upgrade header MUST be provided', $response->getReasonPhrase());
$this->assertEquals('Upgrade', $response->getHeaderLine('Connection'));
$this->assertEquals('websocket', $response->getHeaderLine('Upgrade'));
$this->assertEquals('13', $response->getHeaderLine('Sec-WebSocket-Version'));
}
public function testNoConnectionUpgradeRequested(): void {
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());
$requestText = 'GET / HTTP/1.1
Host: 127.0.0.1:6789
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8
';
$request = Message::parseRequest($requestText);
$response = $negotiator->handshake($request);
$this->assertEquals('1.1', $response->getProtocolVersion());
$this->assertEquals(400, $response->getStatusCode());
$this->assertEquals('Connection Upgrade MUST be requested', $response->getReasonPhrase());
}
public function testInvalidSecWebsocketKey(): void {
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());
$requestText = 'GET / HTTP/1.1
Host: 127.0.0.1:6789
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Sec-WebSocket-Key: 12345
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8
';
$request = Message::parseRequest($requestText);
$response = $negotiator->handshake($request);
$this->assertEquals('1.1', $response->getProtocolVersion());
$this->assertEquals(400, $response->getStatusCode());
$this->assertEquals('Invalid Sec-WebSocket-Key', $response->getReasonPhrase());
}
public function testInvalidSecWebsocketVersion(): void {
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());
$requestText = 'GET / HTTP/1.1
Host: 127.0.0.1:6789
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8
';
$request = Message::parseRequest($requestText);
$response = $negotiator->handshake($request);
$this->assertEquals('1.1', $response->getProtocolVersion());
$this->assertEquals(426, $response->getStatusCode());
$this->assertEquals('Upgrade Required', $response->getReasonPhrase());
$this->assertEquals('Upgrade', $response->getHeaderLine('Connection'));
$this->assertEquals('websocket', $response->getHeaderLine('Upgrade'));
$this->assertEquals('13', $response->getHeaderLine('Sec-WebSocket-Version'));
}
public function testBadSubprotocolResponse(): void {
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());
$negotiator->setStrictSubProtocolCheck(true);
$negotiator->setSupportedSubProtocols([]);
$requestText = 'GET / HTTP/1.1
Host: 127.0.0.1:6789
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: someprotocol
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8
';
$request = Message::parseRequest($requestText);
$response = $negotiator->handshake($request);
$this->assertEquals('1.1', $response->getProtocolVersion());
$this->assertEquals(426, $response->getStatusCode());
$this->assertEquals('No Sec-WebSocket-Protocols requested supported', $response->getReasonPhrase());
$this->assertEquals('Upgrade', $response->getHeaderLine('Connection'));
$this->assertEquals('websocket', $response->getHeaderLine('Upgrade'));
$this->assertEquals('13', $response->getHeaderLine('Sec-WebSocket-Version'));
}
public function testNonStrictSubprotocolDoesNotIncludeHeaderWhenNoneAgreedOn(): void {
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());
$negotiator->setStrictSubProtocolCheck(false);
$negotiator->setSupportedSubProtocols(['someproto']);
$requestText = 'GET / HTTP/1.1
Host: 127.0.0.1:6789
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: someotherproto
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8
';
$request = Message::parseRequest($requestText);
$response = $negotiator->handshake($request);
$this->assertEquals('1.1', $response->getProtocolVersion());
$this->assertEquals(101, $response->getStatusCode());
$this->assertEquals('Upgrade', $response->getHeaderLine('Connection'));
$this->assertEquals('websocket', $response->getHeaderLine('Upgrade'));
$this->assertFalse($response->hasHeader('Sec-WebSocket-Protocol'));
}
public function testSuggestsAppropriateSubprotocol(): void
{
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());
$negotiator->setStrictSubProtocolCheck(true);
$negotiator->setSupportedSubProtocols(['someproto']);
$requestText = 'GET / HTTP/1.1
Host: localhost:8080
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Sec-WebSocket-Key: HGt8eQax7nAOlXUw0/asPQ==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
';
$request = Message::parseRequest($requestText);
$response = $negotiator->handshake($request);
$this->assertEquals('1.1', $response->getProtocolVersion());
$this->assertEquals(426, $response->getStatusCode());
$this->assertEquals('Upgrade', $response->getHeaderLine('Connection'));
$this->assertEquals('websocket', $response->getHeaderLine('Upgrade'));
$this->assertEquals('someproto', $response->getHeaderLine('Sec-WebSocket-Protocol'));
}
}

View File

@@ -0,0 +1,431 @@
<?php
namespace Ratchet\RFC6455\Test\Unit\Messaging;
use Ratchet\RFC6455\Messaging\Frame;
use PHPUnit\Framework\TestCase;
/**
* @covers Ratchet\RFC6455\Messaging\Frame
* @todo getMaskingKey, getPayloadStartingByte don't have tests yet
* @todo Could use some clean up in general, I had to rush to fix a bug for a deadline, sorry.
*/
class FrameTest extends TestCase {
protected $_firstByteFinText = '10000001';
protected $_secondByteMaskedSPL = '11111101';
/** @var Frame */
protected $_frame;
protected $_packer;
protected function setUp(): void {
$this->_frame = new Frame;
}
/**
* Encode the fake binary string to send over the wire
* @param string of 1's and 0's
* @return string
*/
public static function encode(string $in): string {
if (strlen($in) > 8) {
$out = '';
while (strlen($in) >= 8) {
$out .= static::encode(substr($in, 0, 8));
$in = substr($in, 8);
}
return $out;
}
return chr(bindec($in));
}
/**
* This is a data provider
* param string The UTF8 message
* param string The WebSocket framed message, then base64_encoded
*/
public static function UnframeMessageProvider(): array {
return array(
array('Hello World!', 'gYydAIfa1WXrtvIg0LXvbOP7'),
array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg'),
array('ಠ_ಠ', 'gYfnSpu5B/g75gf4Ow=='),
array(
"The quick brown fox jumps over the lazy dog. All work and no play makes Chris a dull boy. I'm trying to get past 128 characters for a unit test here...",
'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY='
)
);
}
public static function underflowProvider(): array {
return array(
array('isFinal', ''),
array('getRsv1', ''),
array('getRsv2', ''),
array('getRsv3', ''),
array('getOpcode', ''),
array('isMasked', '10000001'),
array('getPayloadLength', '10000001'),
array('getPayloadLength', '1000000111111110'),
array('getMaskingKey', '1000000110000111'),
array('getPayload', '100000011000000100011100101010101001100111110100')
);
}
/**
* @dataProvider underflowProvider
*/
public function testUnderflowExceptionFromAllTheMethodsMimickingBuffering(string $method, string $bin): void {
$this->expectException(\UnderflowException::class);
if (!empty($bin)) {
$this->_frame->addBuffer(static::encode($bin));
}
call_user_func(array($this->_frame, $method));
}
/**
* A data provider for testing the first byte of a WebSocket frame
* param bool Given, is the byte indicate this is the final frame
* param int Given, what is the expected opcode
* param string of 0|1 Each character represents a bit in the byte
*/
public static function firstByteProvider(): array {
return array(
array(false, false, false, true, 8, '00011000'),
array(true, false, true, false, 10, '10101010'),
array(false, false, false, false, 15, '00001111'),
array(true, false, false, false, 1, '10000001'),
array(true, true, true, true, 15, '11111111'),
array(true, true, false, false, 7, '11000111')
);
}
/**
* @dataProvider firstByteProvider
*/
public function testFinCodeFromBits(bool $fin, bool $rsv1, bool $rsv2, bool $rsv3, int $opcode, string $bin): void {
$this->_frame->addBuffer(static::encode($bin));
$this->assertEquals($fin, $this->_frame->isFinal());
}
/**
* @dataProvider firstByteProvider
*/
public function testGetRsvFromBits(bool $fin, bool $rsv1, bool $rsv2, bool $rsv3, int $opcode, string $bin): void {
$this->_frame->addBuffer(static::encode($bin));
$this->assertEquals($rsv1, $this->_frame->getRsv1());
$this->assertEquals($rsv2, $this->_frame->getRsv2());
$this->assertEquals($rsv3, $this->_frame->getRsv3());
}
/**
* @dataProvider firstByteProvider
*/
public function testOpcodeFromBits(bool $fin, bool $rsv1, bool $rsv2, bool $rsv3, int $opcode, string $bin): void {
$this->_frame->addBuffer(static::encode($bin));
$this->assertEquals($opcode, $this->_frame->getOpcode());
}
/**
* @dataProvider UnframeMessageProvider
*/
public function testFinCodeFromFullMessage(string $msg, string $encoded): void {
$this->_frame->addBuffer(base64_decode($encoded));
$this->assertTrue($this->_frame->isFinal());
}
/**
* @dataProvider UnframeMessageProvider
*/
public function testOpcodeFromFullMessage(string $msg, string $encoded): void {
$this->_frame->addBuffer(base64_decode($encoded));
$this->assertEquals(1, $this->_frame->getOpcode());
}
public static function payloadLengthDescriptionProvider(): array {
return array(
array(7, '01110101'),
array(7, '01111101'),
array(23, '01111110'),
array(71, '01111111'),
array(7, '00000000'), // Should this throw an exception? Can a payload be empty?
array(7, '00000001')
);
}
/**
* @dataProvider payloadLengthDescriptionProvider
*/
public function testFirstPayloadDesignationValue(int $bits, string $bin): void {
$this->_frame->addBuffer(static::encode($this->_firstByteFinText));
$this->_frame->addBuffer(static::encode($bin));
$ref = new \ReflectionClass($this->_frame);
$cb = $ref->getMethod('getFirstPayloadVal');
$cb->setAccessible(true);
$this->assertEquals(bindec($bin), $cb->invoke($this->_frame));
}
public function testFirstPayloadValUnderflow(): void {
$ref = new \ReflectionClass($this->_frame);
$cb = $ref->getMethod('getFirstPayloadVal');
$cb->setAccessible(true);
$this->expectException(\UnderflowException::class);
$cb->invoke($this->_frame);
}
/**
* @dataProvider payloadLengthDescriptionProvider
*/
public function testDetermineHowManyBitsAreUsedToDescribePayload(int $expected_bits, string $bin): void {
$this->_frame->addBuffer(static::encode($this->_firstByteFinText));
$this->_frame->addBuffer(static::encode($bin));
$ref = new \ReflectionClass($this->_frame);
$cb = $ref->getMethod('getNumPayloadBits');
$cb->setAccessible(true);
$this->assertEquals($expected_bits, $cb->invoke($this->_frame));
}
public function testgetNumPayloadBitsUnderflow(): void {
$ref = new \ReflectionClass($this->_frame);
$cb = $ref->getMethod('getNumPayloadBits');
$cb->setAccessible(true);
$this->expectException(\UnderflowException::class);
$cb->invoke($this->_frame);
}
public function secondByteProvider(): array {
return array(
array(true, 1, '10000001'),
array(false, 1, '00000001'),
array(true, 125, $this->_secondByteMaskedSPL)
);
}
/**
* @dataProvider secondByteProvider
*/
public function testIsMaskedReturnsExpectedValue(bool $masked, int $payload_length, string $bin): void {
$this->_frame->addBuffer(static::encode($this->_firstByteFinText));
$this->_frame->addBuffer(static::encode($bin));
$this->assertEquals($masked, $this->_frame->isMasked());
}
/**
* @dataProvider UnframeMessageProvider
*/
public function testIsMaskedFromFullMessage(string $msg, string $encoded): void {
$this->_frame->addBuffer(base64_decode($encoded));
$this->assertTrue($this->_frame->isMasked());
}
/**
* @dataProvider secondByteProvider
*/
public function testGetPayloadLengthWhenOnlyFirstFrameIsUsed(bool $masked, int $payload_length, string $bin): void {
$this->_frame->addBuffer(static::encode($this->_firstByteFinText));
$this->_frame->addBuffer(static::encode($bin));
$this->assertEquals($payload_length, $this->_frame->getPayloadLength());
}
/**
* @dataProvider UnframeMessageProvider
* @todo Not yet testing when second additional payload length descriptor
*/
public function testGetPayloadLengthFromFullMessage(string $msg, string $encoded): void {
$this->_frame->addBuffer(base64_decode($encoded));
$this->assertEquals(strlen($msg), $this->_frame->getPayloadLength());
}
public function maskingKeyProvider(): array {
$frame = new Frame;
return array(
array($frame->generateMaskingKey()),
array($frame->generateMaskingKey()),
array($frame->generateMaskingKey())
);
}
/**
* @dataProvider maskingKeyProvider
* @todo I I wrote the dataProvider incorrectly, skipping for now
*/
public function testGetMaskingKey(string $mask): void {
$this->_frame->addBuffer(static::encode($this->_firstByteFinText));
$this->_frame->addBuffer(static::encode($this->_secondByteMaskedSPL));
$this->_frame->addBuffer($mask);
$this->assertEquals($mask, $this->_frame->getMaskingKey());
}
public function testGetMaskingKeyOnUnmaskedPayload(): void {
$frame = new Frame('Hello World!');
$this->assertEquals('', $frame->getMaskingKey());
}
/**
* @dataProvider UnframeMessageProvider
* @todo Move this test to bottom as it requires all methods of the class
*/
public function testUnframeFullMessage(string $unframed, string $base_framed): void {
$this->_frame->addBuffer(base64_decode($base_framed));
$this->assertEquals($unframed, $this->_frame->getPayload());
}
public static function messageFragmentProvider(): array {
return array(
array(false, '', '', '', '', '')
);
}
/**
* @dataProvider UnframeMessageProvider
*/
public function testCheckPiecingTogetherMessage(string $msg, string $encoded): void {
$framed = base64_decode($encoded);
for ($i = 0, $len = strlen($framed);$i < $len; $i++) {
$this->_frame->addBuffer(substr($framed, $i, 1));
}
$this->assertEquals($msg, $this->_frame->getPayload());
}
public function testLongCreate(): void {
$len = 65525;
$pl = $this->generateRandomString($len);
$frame = new Frame($pl, true, Frame::OP_PING);
$this->assertTrue($frame->isFinal());
$this->assertEquals(Frame::OP_PING, $frame->getOpcode());
$this->assertFalse($frame->isMasked());
$this->assertEquals($len, $frame->getPayloadLength());
$this->assertEquals($pl, $frame->getPayload());
}
public function testReallyLongCreate(): void {
$len = 65575;
$frame = new Frame($this->generateRandomString($len));
$this->assertEquals($len, $frame->getPayloadLength());
}
public function testExtractOverflow(): void {
$string1 = $this->generateRandomString();
$frame1 = new Frame($string1);
$string2 = $this->generateRandomString();
$frame2 = new Frame($string2);
$cat = new Frame;
$cat->addBuffer($frame1->getContents() . $frame2->getContents());
$this->assertEquals($frame1->getContents(), $cat->getContents());
$this->assertEquals($string1, $cat->getPayload());
$uncat = new Frame;
$uncat->addBuffer($cat->extractOverflow());
$this->assertEquals($string1, $cat->getPayload());
$this->assertEquals($string2, $uncat->getPayload());
}
public function testEmptyExtractOverflow(): void {
$string = $this->generateRandomString();
$frame = new Frame($string);
$this->assertEquals($string, $frame->getPayload());
$this->assertEquals('', $frame->extractOverflow());
$this->assertEquals($string, $frame->getPayload());
}
public function testGetContents(): void {
$msg = 'The quick brown fox jumps over the lazy dog.';
$frame1 = new Frame($msg);
$frame2 = new Frame($msg);
$frame2->maskPayload();
$this->assertNotEquals($frame1->getContents(), $frame2->getContents());
$this->assertEquals(strlen($frame1->getContents()) + 4, strlen($frame2->getContents()));
}
public function testMasking(): void {
$msg = 'The quick brown fox jumps over the lazy dog.';
$frame = new Frame($msg);
$frame->maskPayload();
$this->assertTrue($frame->isMasked());
$this->assertEquals($msg, $frame->getPayload());
}
public function testUnMaskPayload(): void {
$string = $this->generateRandomString();
$frame = new Frame($string);
$frame->maskPayload()->unMaskPayload();
$this->assertFalse($frame->isMasked());
$this->assertEquals($string, $frame->getPayload());
}
public function testGenerateMaskingKey(): void {
$dupe = false;
$done = array();
for ($i = 0; $i < 10; $i++) {
$new = $this->_frame->generateMaskingKey();
if (in_array($new, $done)) {
$dupe = true;
}
$done[] = $new;
}
$this->assertEquals(4, strlen($new));
$this->assertFalse($dupe);
}
public function testGivenMaskIsValid(): void {
$this->expectException(\InvalidArgumentException::class);
$this->_frame->maskPayload('hello world');
}
/**
* @requires extension mbstring
*/
public function testGivenMaskIsValidAscii(): void {
$this->expectException(\OutOfBoundsException::class);
$this->_frame->maskPayload('x✖');
}
protected function generateRandomString(int $length = 10, bool $addSpaces = true, bool $addNumbers = true): string {
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&/()=[]{}'; // ยง
$useChars = array();
for($i = 0; $i < $length; $i++) {
$useChars[] = $characters[mt_rand(0, strlen($characters) - 1)];
}
if($addSpaces === true) {
array_push($useChars, ' ', ' ', ' ', ' ', ' ', ' ');
}
if($addNumbers === true) {
array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9));
}
shuffle($useChars);
$randomString = trim(implode('', $useChars));
$randomString = substr($randomString, 0, $length);
return $randomString;
}
/**
* There was a frame boundary issue when the first 3 bytes of a frame with a payload greater than
* 126 was added to the frame buffer and then Frame::getPayloadLength was called. It would cause the frame
* to set the payload length to 126 and then not recalculate it once the full length information was available.
*
* This is fixed by setting the defPayLen back to -1 before the underflow exception is thrown.
*/
public function testFrameDeliveredOneByteAtATime(): void {
$startHeader = "\x01\x7e\x01\x00"; // header for a text frame of 256 - non-final
$framePayload = str_repeat("*", 256);
$rawOverflow = "xyz";
$rawFrame = $startHeader . $framePayload . $rawOverflow;
$frame = new Frame();
$payloadLen = 256;
for ($i = 0; $i < strlen($rawFrame); $i++) {
$frame->addBuffer($rawFrame[$i]);
try {
// payloadLen will
$payloadLen = $frame->getPayloadLength();
} catch (\UnderflowException $e) {
if ($i > 2) { // we should get an underflow on 0,1,2
$this->fail("Underflow exception when the frame length should be available");
}
}
if ($payloadLen !== 256) {
$this->fail("Payload length of " . $payloadLen . " should have been 256.");
}
}
// make sure the overflow is good
$this->assertEquals($rawOverflow, $frame->extractOverflow());
}
}

View File

@@ -0,0 +1,375 @@
<?php
namespace Ratchet\RFC6455\Test\Unit\Messaging;
use Ratchet\RFC6455\Messaging\CloseFrameChecker;
use Ratchet\RFC6455\Messaging\Frame;
use Ratchet\RFC6455\Messaging\Message;
use Ratchet\RFC6455\Messaging\MessageBuffer;
use React\EventLoop\Factory;
use PHPUnit\Framework\TestCase;
/**
* @covers Ratchet\RFC6455\Messaging\MessageBuffer
*/
class MessageBufferTest extends TestCase
{
/**
* This is to test that MessageBuffer can handle a large receive
* buffer with many many frames without blowing the stack (pre-v0.4 issue)
*/
public function testProcessingLotsOfFramesInASingleChunk(): void {
$frame = new Frame('a', true, Frame::OP_TEXT);
$frameRaw = $frame->getContents();
$data = str_repeat($frameRaw, 1000);
$messageCount = 0;
$messageBuffer = new MessageBuffer(
new CloseFrameChecker(),
function (Message $message) use (&$messageCount): void {
$messageCount++;
$this->assertEquals('a', $message->getPayload());
},
null,
false
);
$messageBuffer->onData($data);
$this->assertEquals(1000, $messageCount);
}
public function testProcessingMessagesAsynchronouslyWhileBlockingInMessageHandler(): void {
$loop = Factory::create();
$frameA = new Frame('a', true, Frame::OP_TEXT);
$frameB = new Frame('b', true, Frame::OP_TEXT);
$bReceived = false;
$messageBuffer = new MessageBuffer(
new CloseFrameChecker(),
static function (Message $message) use (&$messageCount, &$bReceived, $loop): void {
$payload = $message->getPayload();
$bReceived = $payload === 'b';
if (!$bReceived) {
$loop->run();
}
},
null,
false
);
$loop->addPeriodicTimer(0.1, static function () use ($messageBuffer, $frameB, $loop): void {
$loop->stop();
$messageBuffer->onData($frameB->getContents());
});
$messageBuffer->onData($frameA->getContents());
$this->assertTrue($bReceived);
}
public function testInvalidFrameLength(): void {
$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(),
static function (Message $message) use (&$messageCount): void {
$messageCount++;
},
function (Frame $frame) use (&$controlFrame) {
$this->assertNull($controlFrame);
$controlFrame = $frame;
},
false,
null,
0,
10
);
$messageBuffer->onData($frameRaw);
$this->assertEquals(0, $messageCount);
$this->assertInstanceOf(Frame::class, $controlFrame);
$this->assertEquals(Frame::OP_CLOSE, $controlFrame->getOpcode());
$this->assertEquals([Frame::CLOSE_PROTOCOL], array_merge(unpack('n*', substr($controlFrame->getPayload(), 0, 2))));
}
public function testFrameLengthTooBig(): void {
$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(),
static function (Message $message) use (&$messageCount): void {
$messageCount++;
},
function (Frame $frame) use (&$controlFrame): void {
$this->assertNull($controlFrame);
$controlFrame = $frame;
},
false,
null,
0,
10
);
$messageBuffer->onData($frameRaw);
$this->assertEquals(0, $messageCount);
$this->assertInstanceOf(Frame::class, $controlFrame);
$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(): void {
$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(),
static function (Message $message) use (&$messageCount): void {
$messageCount++;
},
function (Frame $frame) use (&$controlFrame): void {
$this->assertNull($controlFrame);
$controlFrame = $frame;
},
false,
null,
100,
0
);
$messageBuffer->onData($frameRaw);
$this->assertEquals(0, $messageCount);
$this->assertInstanceOf(Frame::class, $controlFrame);
$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(): void {
$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(),
static function (Message $message) use (&$messageCount): void {
$messageCount++;
},
function (Frame $frame) use (&$controlFrame): void {
$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->assertInstanceOf(Frame::class, $controlFrame);
$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(string $phpConfigurationValue, int $expectedLimit): void {
$method = new \ReflectionMethod(MessageBuffer::class, 'getMemoryLimit');
$method->setAccessible(true);
$actualLimit = $method->invoke(null, $phpConfigurationValue);
$this->assertSame($expectedLimit, $actualLimit);
}
public function phpConfigurationProvider(): array {
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', 0],
'invalid float value' => ['2.5M', 2 * 1024 * 1024],
'empty value' => ['', 0],
'invalid ini setting' => ['whatever it takes', 0]
];
}
public function testInvalidMaxFramePayloadSizes(): void {
$this->expectException(\InvalidArgumentException::class);
new MessageBuffer(
new CloseFrameChecker(),
static function (Message $message): void {},
static function (Frame $frame): void {},
false,
null,
0,
-1
);
}
public function testInvalidMaxMessagePayloadSizes(): void {
$this->expectException(\InvalidArgumentException::class);
new MessageBuffer(
new CloseFrameChecker(),
static function (Message $message): void {},
static function (Frame $frame): void {},
false,
null,
-1,
0
);
}
/**
* @dataProvider phpConfigurationProvider
*
* @param string $phpConfigurationValue
* @param int $expectedLimit
*
* @runInSeparateProcess
*/
public function testIniSizes(string $phpConfigurationValue, int $expectedLimit): void {
$value = @ini_set('memory_limit', $phpConfigurationValue);
if ($value === false) {
$this->markTestSkipped("Does not support setting the memory_limit lower than current memory_usage");
}
$messageBuffer = new MessageBuffer(
new CloseFrameChecker(),
static function (Message $message): void {},
static function (Frame $frame): void {},
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(): void {
$value = @ini_set('memory_limit', 'lots of memory');
if ($value === false) {
$this->markTestSkipped("Does not support setting the memory_limit lower than current memory_usage");
}
$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));
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Ratchet\RFC6455\Test\Unit\Messaging;
use Ratchet\RFC6455\Messaging\Frame;
use Ratchet\RFC6455\Messaging\Message;
use PHPUnit\Framework\TestCase;
/**
* @covers Ratchet\RFC6455\Messaging\Message
*/
class MessageTest extends TestCase {
/** @var Message */
protected $message;
protected function setUp(): void {
$this->message = new Message;
}
public function testNoFrames(): void {
$this->assertFalse($this->message->isCoalesced());
}
public function testNoFramesOpCode(): void {
$this->expectException(\UnderflowException::class);
$this->message->getOpCode();
}
public function testFragmentationPayload(): void {
$a = 'Hello ';
$b = 'World!';
$f1 = new Frame($a, false);
$f2 = new Frame($b, true, Frame::OP_CONTINUE);
$this->message->addFrame($f1)->addFrame($f2);
$this->assertEquals(strlen($a . $b), $this->message->getPayloadLength());
$this->assertEquals($a . $b, $this->message->getPayload());
}
public function testUnbufferedFragment(): void {
$this->message->addFrame(new Frame('The quick brow', false));
$this->expectException(\UnderflowException::class);
$this->message->getPayload();
}
public function testGetOpCode(): void {
$this->message
->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT))
->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE))
->addFrame(new Frame('er the lazy dog', true, Frame::OP_CONTINUE))
;
$this->assertEquals(Frame::OP_TEXT, $this->message->getOpCode());
}
public function testGetUnBufferedPayloadLength(): void {
$this->message
->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT))
->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE))
;
$this->assertEquals(28, $this->message->getPayloadLength());
}
}