Passing Autobahn tests (except compression)

This commit is contained in:
matt 2015-03-18 11:12:11 -04:00
parent 9890a8d7e1
commit c2a51b62ef
8 changed files with 136 additions and 76 deletions

View File

@ -61,7 +61,6 @@ class Frame implements FrameInterface {
*/
protected $secondByte = -1;
/**
* @param string|null $payload
* @param bool $final
@ -446,4 +445,34 @@ class Frame implements FrameInterface {
return '';
}
/**
* Determine if a close code is valid
* @param int|string
* @return bool
*/
public function isValidCloseCode($val) {
if (in_array($val, [
static::CLOSE_NORMAL,
static::CLOSE_GOING_AWAY,
static::CLOSE_PROTOCOL,
static::CLOSE_BAD_DATA,
//static::CLOSE_NO_STATUS,
//static::CLOSE_ABNORMAL,
static::CLOSE_BAD_PAYLOAD,
static::CLOSE_POLICY,
static::CLOSE_TOO_BIG,
static::CLOSE_MAND_EXT,
static::CLOSE_SRV_ERR,
//static::CLOSE_TLS,
])) {
return true;
}
if ($val >= 3000 && $val <= 4999) {
return true;
}
return false;
}
}

View File

@ -53,15 +53,29 @@ class Message implements MessageInterface {
/**
* {@inheritdoc}
* @todo Also, I should perhaps check the type...control frames (ping/pong/close) are not to be considered part of a message
* @todo What should we do if there are binary and text mixed together?
*/
public function addFrame(FrameInterface $fragment) {
// should the validation stuff be somewhere else?
// it really needs the context of the message to know whether there is a problem
if ($this->_frames->isEmpty()) {
$this->binary = $fragment->getOpcode() == Frame::OP_BINARY;
}
// check to see if this is a continuation frame when there is no
// frames yet added
if ($this->_frames->count() == 0 && $fragment->getOpcode() == Frame::OP_CONTINUE) {
return Frame::CLOSE_PROTOCOL;
}
// check to see if this is not a continuation frame when there is already frames
if ($this->_frames->count() > 0 && $fragment->getOpcode() != Frame::OP_CONTINUE) {
return Frame::CLOSE_PROTOCOL;
}
$this->_frames->push($fragment);
return $this;
return true;
//return $this;
}
/**
@ -106,6 +120,8 @@ class Message implements MessageInterface {
$buffer .= $frame->getPayload();
}
echo "Reassembled " . strlen($buffer) . " bytes in " . $this->_frames->count() . " frames\n";
return $buffer;
}

View File

@ -12,4 +12,9 @@ interface MessageInterface extends DataInterface, \ArrayAccess, \Countable {
* @return int
*/
function getOpcode();
/**
* @return bool
*/
function isBinary();
}

View File

@ -4,8 +4,10 @@ namespace Ratchet\RFC6455\Messaging\Streaming;
use Evenement\EventEmitterInterface;
use Evenement\EventEmitterTrait;
use Ratchet\RFC6455\Encoding\Validator;
use Ratchet\RFC6455\Messaging\Protocol\Frame;
use Ratchet\RFC6455\Messaging\Protocol\Message;
use Ratchet\RFC6455\Messaging\Validation\MessageValidator;
class MessageStreamer implements EventEmitterInterface {
use EventEmitterTrait;
@ -16,12 +18,16 @@ class MessageStreamer implements EventEmitterInterface {
/** @var Message */
private $currentMessage;
/** @var array */
private $closeCodes = [];
/** @var MessageValidator */
private $validator;
function __construct()
/** @var bool */
private $checkForMask;
function __construct($client = false)
{
$this->setCloseCodes();
$this->checkForMask = !$client;
$this->validator = new MessageValidator(new Validator(), $this->checkForMask);
}
@ -41,6 +47,12 @@ class MessageStreamer implements EventEmitterInterface {
$frame->addBuffer($data);
if ($frame->isCoalesced()) {
$validFrame = $this->validator->validateFrame($frame);
if ($validFrame !== true) {
$this->emit('close', [$validFrame]);
return;
}
$opcode = $frame->getOpcode();
if ($opcode > 2) {
if ($frame->getPayloadLength() > 125) {
@ -63,7 +75,7 @@ class MessageStreamer implements EventEmitterInterface {
list($closeCode) = array_merge(unpack('n*', substr($bin, 0, 2)));
}
if (!$this->isValidCloseCode($closeCode)) {
if (!$frame->isValidCloseCode($closeCode)) {
$this->emit('close', [$frame::CLOSE_PROTOCOL]);
return;
}
@ -104,11 +116,20 @@ class MessageStreamer implements EventEmitterInterface {
$overflow = $frame->extractOverflow();
$this->currentMessage->addFrame($this->currentFrame);
$frameAdded = $this->currentMessage->addFrame($this->currentFrame);
if ($frameAdded !== true) {
$this->emit('close', [$frameAdded]);
}
unset($this->currentFrame);
}
if ($this->currentMessage->isCoalesced()) {
$msgCheck = $this->validator->checkMessage($this->currentMessage);
if ($msgCheck !== true) {
if ($msgCheck === false) $msgCheck = null;
$this->emit('close', [$msgCheck]);
return;
}
$this->emit('message', [$this->currentMessage]);
//$parsed = $from->WebSocket->message->getPayload();
unset($this->currentMessage);
@ -135,39 +156,4 @@ class MessageStreamer implements EventEmitterInterface {
public function newFrame($payload = null, $final = null, $opcode = null) {
return new Frame($payload, $final, $opcode);
}
/**
* Determine if a close code is valid
* @param int|string
* @return bool
*/
public function isValidCloseCode($val) {
if (array_key_exists($val, $this->closeCodes)) {
return true;
}
if ($val >= 3000 && $val <= 4999) {
return true;
}
return false;
}
/**
* Creates a private lookup of valid, private close codes
*/
protected function setCloseCodes() {
$this->closeCodes[Frame::CLOSE_NORMAL] = true;
$this->closeCodes[Frame::CLOSE_GOING_AWAY] = true;
$this->closeCodes[Frame::CLOSE_PROTOCOL] = true;
$this->closeCodes[Frame::CLOSE_BAD_DATA] = true;
//$this->closeCodes[Frame::CLOSE_NO_STATUS] = true;
//$this->closeCodes[Frame::CLOSE_ABNORMAL] = true;
$this->closeCodes[Frame::CLOSE_BAD_PAYLOAD] = true;
$this->closeCodes[Frame::CLOSE_POLICY] = true;
$this->closeCodes[Frame::CLOSE_TOO_BIG] = true;
$this->closeCodes[Frame::CLOSE_MAND_EXT] = true;
$this->closeCodes[Frame::CLOSE_SRV_ERR] = true;
//$this->closeCodes[Frame::CLOSE_TLS] = true;
}
}
}

View File

@ -1,15 +1,18 @@
<?php
namespace Ratchet\RFC6455\Messaging\Validation;
use Ratchet\RFC6455\Encoding\ValidatorInterface;
use Ratchet\RFC6455\Messaging\Protocol\Frame;
use Ratchet\RFC6455\Messaging\Protocol\FrameInterface;
use Ratchet\RFC6455\Messaging\Protocol\MessageInterface;
class MessageValidator {
public $checkForMask = true;
public $checkForMask;
private $validator;
public function __construct(ValidatorInterface $validator) {
public function __construct(ValidatorInterface $validator, $checkForMask = true) {
$this->validator = $validator;
$this->checkForMask = $checkForMask;
}
/**
@ -25,7 +28,7 @@ class MessageValidator {
$frame = $message[0];
$frameCheck = $this->checkFrame($frame);
$frameCheck = $this->validateFrame($frame);
if (true !== $frameCheck) {
return $frameCheck;
}
@ -35,19 +38,22 @@ class MessageValidator {
return $frame::CLOSE_PROTOCOL;
}
if (count($message) > 0 && $frame::OP_CONTINUE !== $frame->getOpcode()) {
return $frame::CLOSE_PROTOCOL;
}
// I (mbonneau) don't understand this - seems to always kill the tests
// if (count($message) > 0 && $frame::OP_CONTINUE !== $frame->getOpcode()) {
// return $frame::CLOSE_PROTOCOL;
// }
$parsed = $message->getPayload();
if (!$this->validator->checkEncoding($parsed, 'UTF-8')) {
return $frame::CLOSE_BAD_PAYLOAD;
if (!$message->isBinary()) {
$parsed = $message->getPayload();
if (!$this->validator->checkEncoding($parsed, 'UTF-8')) {
return $frame::CLOSE_BAD_PAYLOAD;
}
}
return true;
}
public function validateFrame(FrameInterface $frame) {
public function validateFrame(Frame $frame) {
if (false !== $frame->getRsv1() ||
false !== $frame->getRsv2() ||
false !== $frame->getRsv3()
@ -73,15 +79,20 @@ class MessageValidator {
$bin = $frame->getPayload();
if (empty($bin)) {
return $frame::CLOSE_NORMAL;
}
if (strlen($bin) == 1) {
return $frame::CLOSE_PROTOCOL;
}
if (strlen($bin) >= 2) {
list($closeCode) = array_merge(unpack('n*', substr($bin, 0, 2)));
}
if (!$this->isValidCloseCode($closeCode)) {
if (!$frame->isValidCloseCode($closeCode)) {
return $frame::CLOSE_PROTOCOL;
}

View File

@ -26,7 +26,7 @@ function getTestCases() {
$rawResponse = "";
$response = null;
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer();
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer(true);
$ms->on('message', function (Message $msg) use ($stream, $deferred) {
$deferred->resolve($msg->getPayload());
@ -38,7 +38,7 @@ function getTestCases() {
$ms->on('close', function ($code) use ($stream) {
if ($code === null) {
$stream->close();
$stream->end();
return;
}
$frame = new Frame(pack('n', $code), true, Frame::OP_CLOSE);
@ -82,16 +82,17 @@ function runTest($case)
$deferred = new Deferred();
$factory->create('127.0.0.1', 9001)->then(function (\React\Stream\Stream $stream) use ($deferred, $casePath) {
$factory->create('127.0.0.1', 9001)->then(function (\React\Stream\Stream $stream) use ($deferred, $casePath, $case) {
$cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator($casePath);
$cnRequest = $cn->getRequest();
$rawResponse = "";
$response = null;
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer();
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer(true);
$ms->on('message', function (Message $msg) use ($stream, $deferred) {
$ms->on('message', function (Message $msg) use ($stream, $deferred, $case) {
echo "Got message on case " . $case . "\n";
$opcode = $msg->isBinary() ? Frame::OP_BINARY : Frame::OP_TEXT;
$frame = new Frame($msg->getPayload(), true, $opcode);
$frame->maskPayload();
@ -107,7 +108,7 @@ function runTest($case)
$ms->on('close', function ($code) use ($stream, $deferred) {
if ($code === null) {
$stream->close();
$stream->end();
return;
}
$frame = new Frame(pack('n', $code), true, Frame::OP_CLOSE);
@ -161,7 +162,7 @@ function createReport() {
$rawResponse = "";
$response = null;
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer();
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer(true);
$ms->on('message', function (Message $msg) use ($stream, $deferred) {
$deferred->resolve($msg->getPayload());
@ -173,7 +174,7 @@ function createReport() {
$ms->on('close', function ($code) use ($stream) {
if ($code === null) {
$stream->close();
$stream->end();
return;
}
$frame = new Frame(pack('n', $code), true, Frame::OP_CLOSE);
@ -212,14 +213,26 @@ function createReport() {
$testPromises = [];
getTestCases()->then(function ($count) {
getTestCases()->then(function ($count) use ($loop) {
echo "Running " . $count . " test cases.\n";
for ($i = 0; $i < $count; $i++) {
$testPromises[] = runTest($i + 1);
}
$allDeferred = new Deferred();
\React\Promise\all($testPromises)->then(function () {
$runNextCase = function () use (&$i, &$runNextCase, $count, $allDeferred) {
$i++;
if ($i > $count) {
$allDeferred->resolve();
return;
}
echo "Running " . $i . "\n";
runTest($i)->then($runNextCase);
};
$i = 0;
$runNextCase();
$allDeferred->promise()->then(function () {
echo "Generating report...\n";
createReport();
});
});

View File

@ -8,8 +8,7 @@
"options": {"version": 18}}
],
"casesy": ["1.1.1", "1.1.2"],
"cases": ["1.*", "2.*", "3.*", "4.*","5.*"],
"exclude-cases": [],
"cases": ["1.*"],
"exclude-cases": ["12.*","13.*"],
"exclude-agent-cases": {}
}

View File

@ -4,9 +4,10 @@
"failByDrop": false
}
, "outdir": "./reports/clients"
, "casesy": ["*"]
, "cases": ["1.*", "2.*", "3.*", "4.*", "5.*"]
, "casesx": ["1.1.8"]
, "exclude-cases": ["9.8.6"]
, "casesa": ["*"]
, "casesj": ["9.3.2", "9.3.3", "9.3.4", "9.4.2", "9.4.3", "9.4.4", "9.7.6", "9.8.6"]
, "cases": ["9.3.1", "9.3.2"]
, "casesx": ["1.*","2.*"]
, "exclude-cases": ["12.*", "13.*"]
, "exclude-agent-cases": {}
}