diff --git a/lib/Ratchet/Application/WebSocket/App.php b/lib/Ratchet/Application/WebSocket/App.php index 2a75987..b398d8d 100644 --- a/lib/Ratchet/Application/WebSocket/App.php +++ b/lib/Ratchet/Application/WebSocket/App.php @@ -101,14 +101,35 @@ class App implements ApplicationInterface, ConfiguratorInterface { return $comp; } - // buffer! - - $msg = $from->WebSocket->version->unframe($msg); - if (is_array($msg)) { // temporary - $msg = $msg['payload']; + if (!isset($from->WebSocket->message)) { + $from->WebSocket->message = $from->WebSocket->version->newMessage(); } - return $this->prepareCommand($this->_app->onRecv($from, $msg)); + // There is a frame fragment attatched to the connection, add to it + if (!isset($from->WebSocket->frame)) { + $from->WebSocket->frame = $from->WebSocket->version->newFrame(); + } + + $from->WebSocket->frame->addBuffer($msg); + if ($from->WebSocket->frame->isCoalesced()) { + if ($from->WebSocket->frame->getOpcode() > 2) { + throw new \UnexpectedValueException('Control frame support coming soon!'); + } + // Check frame + // If is control frame, do your thing + // Else, add to message + // Control frames (ping, pong, close) can be sent in between a fragmented message + + $from->WebSocket->message->addFrame($from->WebSocket->frame); + unset($from->WebSocket->frame); + } + + if ($from->WebSocket->message->isCoalesced()) { + $cmds = $this->prepareCommand($this->_app->onRecv($from, (string)$from->WebSocket->message)); + unset($from->WebSocket->message); + + return $cmds; + } } public function onClose(Connection $conn) { @@ -153,7 +174,7 @@ class App implements ApplicationInterface, ConfiguratorInterface { * @todo Can/will add more versions later, but perhaps a chain of responsibility, ask each version if they want to handle the request */ protected function getVersion($message) { - if (false === strstr($message, "\r\n\r\n")) { // This _could_ fail with Hixie + if (false === strstr($message, "\r\n\r\n")) { // This CAN fail with Hixie, depending on the TCP buffer in between throw new \UnderflowException; } diff --git a/lib/Ratchet/Application/WebSocket/FrameInterface.php b/lib/Ratchet/Application/WebSocket/Version/FrameInterface.php similarity index 56% rename from lib/Ratchet/Application/WebSocket/FrameInterface.php rename to lib/Ratchet/Application/WebSocket/Version/FrameInterface.php index 5b500e5..3056094 100644 --- a/lib/Ratchet/Application/WebSocket/FrameInterface.php +++ b/lib/Ratchet/Application/WebSocket/Version/FrameInterface.php @@ -1,7 +1,18 @@ <?php -namespace Ratchet\Protocol\WebSocket; +namespace Ratchet\Application\WebSocket\Version; interface FrameInterface { + /** + * @alias getPayload + */ + function __toString(); + + /** + * Dunno if I'll use this + * Thinking could be used if a control frame? + */ +// function __invoke(); + /** * @return bool */ @@ -9,18 +20,19 @@ interface FrameInterface { /** * @param string + * @todo Theoretically, there won't be a buffer overflow (end of frame + start of new frame) - but test later, return a string with overflow here */ function addBuffer($buf); /** * @return bool */ - function isFragment(); +// function isFragment(); /** * @return bool */ - function isFinial(); + function isFinal(); /** * @return bool @@ -40,7 +52,7 @@ interface FrameInterface { /** * @return int */ - function getReceivedPayloadLength(); +// function getReceivedPayloadLength(); /** * 32-big string diff --git a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php index 8ff0da9..e83e9b4 100644 --- a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php +++ b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php @@ -2,6 +2,13 @@ namespace Ratchet\Application\WebSocket\Version; /** + * FOR THE LOVE OF BEER, PLEASE PLEASE PLEASE DON'T allow the use of this in your application! + * Hixie76 is bad for 2 (there's more) reasons: + * 1) The handshake is done in HTTP, which includes a key for signing in the body... + * BUT there is no Length defined in the header (as per HTTP spec) so the TCP buffer can't tell when the message is done! + * 2) By nature it's insecure. Google did a test study where they were able to do a + * man-in-the-middle attack on 10%-15% of the people who saw their add who had a browser (currently only Safari) supporting the Hixie76 protocol. + * This was exploited by taking advantage of proxy servers in front of the user who ignored some HTTP headers in the handshake * The Hixie76 is currently implemented by Safari * Handshake from Andrea Giammarchi (http://webreflection.blogspot.com/2010/06/websocket-handshake-76-simplified.html) * @link http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 @@ -34,6 +41,14 @@ class Hixie76 implements VersionInterface { ; } + public function newMessage() { + return new Hixie76\Message; + } + + public function newFrame() { + return new Hixie76\Frame; + } + public function unframe($message) { return substr($message, 1, strlen($message) - 2); } diff --git a/lib/Ratchet/Application/WebSocket/Version/Hixie76/Frame.php b/lib/Ratchet/Application/WebSocket/Version/Hixie76/Frame.php new file mode 100644 index 0000000..4fcdec8 --- /dev/null +++ b/lib/Ratchet/Application/WebSocket/Version/Hixie76/Frame.php @@ -0,0 +1,58 @@ +<?php +namespace Ratchet\Application\WebSocket\Version\Hixie76; +use Ratchet\Application\WebSocket\Version\FrameInterface; + +/** + * This does not entirely follow the protocol to spec, but (mostly) works + * Hixie76 probably should not even be supported + */ +class Frame implements FrameInterface { + /** + * @type string + */ + protected $_data = ''; + + public function __toString() { + return $this->getPayload(); + } + + public function isCoalesced() { + return (boolean)($this->_data[0] == chr(0) && substr($this->_data, -1) == chr(255)); + } + + public function addBuffer($buf) { + $this->_data .= (string)$buf; + } + + public function isFinal() { + return true; + } + + public function isMasked() { + return false; + } + + public function getOpcode() { + return 1; + } + + public function getPayloadLength() { + if (!$this->isCoalesced()) { + throw new \UnderflowException('Not enough of the message has been buffered to determine the length of the payload'); + } + + return strlen($this->_data) - 2; + } + + public function getMaskingKey() { + return ''; + } + + public function getPayload() { + if (!$this->isCoalesced()) { + return new \UnderflowException('Not enough data buffered to read payload'); + } + + return substr($this->_data, 1, strlen($this->_data) - 2); + } +} \ No newline at end of file diff --git a/lib/Ratchet/Application/WebSocket/Version/Hixie76/Message.php b/lib/Ratchet/Application/WebSocket/Version/Hixie76/Message.php new file mode 100644 index 0000000..6366c22 --- /dev/null +++ b/lib/Ratchet/Application/WebSocket/Version/Hixie76/Message.php @@ -0,0 +1,48 @@ +<?php +namespace Ratchet\Application\WebSocket\Version\Hixie76; +use Ratchet\Application\WebSocket\Version\MessageInterface; +use Ratchet\Application\WebSocket\Version\FrameInterface; + +class Message implements MessageInterface { + /** + * @var Ratchet\Application\WebSocket\Version\FrameInterface + */ + protected $_frame = null; + + public function __toString() { + return $this->getPayload(); + } + + public function isCoalesced() { + if (!($this->_frame instanceof FrameInterface)) { + return false; + } + + return $this->_frame->isCoalesced(); + } + + public function addFrame(FrameInterface $fragment) { + if (null !== $this->_frame) { + throw new \OverflowException('Hixie76 does not support multiple framing of messages'); + } + + $this->_frame = $fragment; + } + + public function getOpcode() { + // Hixie76 only supported text messages + return 1; + } + + public function getPayloadLength() { + throw new \DomainException('Please sir, may I have some code? (' . __FUNCTION__ . ')'); + } + + public function getPayload() { + if (!$this->isCoalesced()) { + throw new \UnderflowException('Message has not been fully buffered yet'); + } + + return $this->_frame->getPayload(); + } +} \ No newline at end of file diff --git a/lib/Ratchet/Application/WebSocket/Version/HyBi10.php b/lib/Ratchet/Application/WebSocket/Version/HyBi10.php index 6077879..e4bba6d 100644 --- a/lib/Ratchet/Application/WebSocket/Version/HyBi10.php +++ b/lib/Ratchet/Application/WebSocket/Version/HyBi10.php @@ -5,12 +5,14 @@ use Ratchet\Application\WebSocket\Util\HTTP; /** * The HyBi-10 version, identified in the headers as version 8, is currently implemented by the latest Chrome and Firefix version * @link http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 + * @todo Naming...I'm not fond of this naming convention... */ class HyBi10 implements VersionInterface { const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; /** * @return array + * I kept this as an array and combined in App for future considerations...easier to add a subprotol as a key value than edit a string */ public function handshake($message) { $headers = HTTP::getHeaders($message); @@ -25,6 +27,20 @@ class HyBi10 implements VersionInterface { ); } + /** + * @return HyBi10\Message + */ + public function newMessage() { + return new HyBi10\Message; + } + + /** + * @return HyBi10\Frame + */ + public function newFrame() { + return new HyBi10\Frame; + } + /** * Unframe a message received from the client * Thanks to @lemmingzshadow for the code on decoding a HyBi-10 frame diff --git a/lib/Ratchet/Application/WebSocket/Version/HyBi10/Frame.php b/lib/Ratchet/Application/WebSocket/Version/HyBi10/Frame.php new file mode 100644 index 0000000..5223a96 --- /dev/null +++ b/lib/Ratchet/Application/WebSocket/Version/HyBi10/Frame.php @@ -0,0 +1,203 @@ +<?php +namespace Ratchet\Application\WebSocket\Version\HyBi10; +use Ratchet\Application\WebSocket\Version\FrameInterface; + +class Frame implements FrameInterface { + /** + * The contents of the frame + * @var string + */ + protected $_data = ''; + + /** + * Number of bytes received from the frame + * @var int + */ + public $_bytes_rec = 0; + + /** + * Number of bytes in the payload (as per framing protocol) + * @var int + */ + protected $_pay_len_def = -1; + + /** + * Bit 9-15 + * @var int + */ + protected $_pay_check = -1; + + public function __toString() { + return (string)$this->getPayload(); + } + + public function isCoalesced() { + try { + $payload_length = $this->getPayloadLength(); + $payload_start = $this->getPayloadStartingByte(); + } catch (\UnderflowException $e) { + return false; + } + + return $payload_length + $payload_start === $this->_bytes_rec; + } + + public function addBuffer($buf) { + $buf = (string)$buf; + + $this->_data .= $buf; + $this->_bytes_rec += strlen($buf); + } + + public function isFinal() { + if ($this->_bytes_rec < 1) { + throw new \UnderflowException('Not enough bytes received to determine if this is the final frame in message'); + } + + $fbb = sprintf('%08b', ord($this->_data[0])); + return (boolean)(int)$fbb[0]; + } + + public function isMasked() { + if ($this->_bytes_rec < 2) { + throw new \UnderflowException("Not enough bytes received ({$this->_bytes_rec}) to determine if mask is set"); + } + + return (boolean)bindec(substr(sprintf('%08b', ord($this->_data[1])), 0, 1)); + } + + public function getOpcode() { + if ($this->_bytes_rec < 1) { + throw new \UnderflowException('Not enough bytes received to determine opcode'); + } + + return bindec(substr(sprintf('%08b', ord($this->_data[0])), 4, 4)); + } + + /** + * Gets the decimal value of bits 9 (10th) through 15 inclusive + * @return int + * @throws UnderflowException If the buffer doesn't have enough data to determine this + */ + protected function getFirstPayloadVal() { + if ($this->_bytes_rec < 2) { + throw new \UnderflowException('Not enough bytes received'); + } + + return ord($this->_data[1]) & 127; + } + + /** + * @return int (7|23|71) Number of bits defined for the payload length in the fame + * @throws UnderflowException + */ + protected function getNumPayloadBits() { + if ($this->_bytes_rec < 2) { + throw new \UnderflowException('Not enough bytes received'); + } + + // By default 7 bits are used to describe the payload length + // These are bits 9 (10th) through 15 inclusive + $bits = 7; + + // Get the value of those bits + $check = $this->getFirstPayloadVal(); + + // If the value is 126 the 7 bits plus the next 16 are used to describe the payload length + if ($check >= 126) { + $bits += 16; + } + + // If the value of the initial payload length are is 127 an additional 48 bits are used to describe length + // Note: The documentation specifies the length is to be 63 bits, but I think that's a type and is 64 (16+48) + if ($check === 127) { + $bits += 48; + } + + if (!in_array($bits, array(7, 23, 71))) { + throw new \UnexpectedValueException("Malformed frame, invalid payload length provided"); + } + + return $bits; + } + + /** + * This just returns the number of bytes used in the frame to describe the payload length (as opposed to # of bits) + * @see getNumPayloadBits + */ + protected function getNumPayloadBytes() { + return (1 + $this->getNumPayloadBits()) / 8; + } + + public function getPayloadLength() { + if ($this->_pay_len_def !== -1) { + return $this->_pay_len_def; + } + + $length_check = $this->getFirstPayloadVal(); + + if ($length_check <= 125) { + $this->_pay_len_def = $length_check; + return $this->getPayloadLength(); + } + + $byte_length = $this->getNumPayloadBytes(); + if ($this->_bytes_rec < 1 + $byte_length) { + throw new \UnderflowException('Not enough data buffered to determine payload length'); + } + + $strings = array(); + for ($i = 2; $i < $byte_length + 1; $i++) { + $strings[] = ord($this->_data[$i]); + } + + $this->_pay_len_def = bindec(vsprintf(str_repeat('%08b', $byte_length - 1), $strings)); + return $this->getPayloadLength(); + } + + public function getMaskingKey() { + if (!$this->isMasked()) { + return ''; + } + + $length = 4; + $start = 1 + $this->getNumPayloadBytes(); + + if ($this->_bytes_rec < $start + $length) { + throw new \UnderflowException('Not enough data buffered to calculate the masking key'); + } + + return substr($this->_data, $start, $length); + } + + public function getPayloadStartingByte() { + return 1 + $this->getNumPayloadBytes() + strlen($this->getMaskingKey()); + } + + public function getPayload() { + if (!$this->isCoalesced()) { + throw new \UnderflowException('Can not return partial message'); + } + + $payload = ''; + $length = $this->getPayloadLength(); + + if ($this->isMasked()) { + $mask = $this->getMaskingKey(); + $start = $this->getPayloadStartingByte(); + + for ($i = 0; $i < $length; $i++) { + $payload .= $this->_data[$i + $start] ^ $mask[$i % 4]; + } + } else { + $payload = substr($this->_data, $start, $this->getPayloadLength()); + } + + if (strlen($payload) !== $length) { + // Is this possible? isCoalesced() math _should_ ensure if there is mal-formed data, it would return false + throw new \UnexpectedValueException('Payload length does not match expected length'); + } + + return $payload; + } +} \ No newline at end of file diff --git a/lib/Ratchet/Application/WebSocket/Version/HyBi10/Message.php b/lib/Ratchet/Application/WebSocket/Version/HyBi10/Message.php new file mode 100644 index 0000000..ccca59f --- /dev/null +++ b/lib/Ratchet/Application/WebSocket/Version/HyBi10/Message.php @@ -0,0 +1,63 @@ +<?php +namespace Ratchet\Application\WebSocket\Version\HyBi10; +use Ratchet\Application\WebSocket\Version\MessageInterface; +use Ratchet\Application\WebSocket\Version\FrameInterface; + +class Message implements MessageInterface { + /** + * @var SplDoublyLinkedList + */ + protected $_frames; + + public function __construct() { + $this->_frames = new \SplDoublyLinkedList; + } + + public function __toString() { + return $this->getPayload(); + } + + public function isCoalesced() { + if (count($this->_frames) == 0) { + return false; + } + + $last = $this->_frames->top(); + + return ($last->isCoalesced() && $last->isFinal()); + } + + /** + * @todo Should I allow addFrame if the frame is not coalesced yet? I believe I'm assuming this class will only receive fully formed frame messages + * @todo Also, I should perhaps check the type...control frames (ping/pong/close) are not to be considered part of a message + */ + public function addFrame(FrameInterface $fragment) { + $this->_frames->push($fragment); + } + + public function getOpcode() { + if (count($this->_frames) == 0) { + throw new \UnderflowException('No frames have been added to this message'); + } + + return $this->_frames->bottom()->getOpcode(); + } + + public function getPayloadLength() { + throw new \DomainException('Please sir, may I have some code? (' . __FUNCTION__ . ')'); + } + + public function getPayload() { + if (!$this->isCoalesced()) { + throw new \UnderflowMessage('Message has not been put back together yet'); + } + + $buffer = ''; + + foreach ($this->_frames as $frame) { + $buffer .= (string)$frame; + } + + return $buffer; + } +} \ No newline at end of file diff --git a/lib/Ratchet/Application/WebSocket/MessageInterface.php b/lib/Ratchet/Application/WebSocket/Version/MessageInterface.php similarity index 74% rename from lib/Ratchet/Application/WebSocket/MessageInterface.php rename to lib/Ratchet/Application/WebSocket/Version/MessageInterface.php index 9053fb2..57cba5d 100644 --- a/lib/Ratchet/Application/WebSocket/MessageInterface.php +++ b/lib/Ratchet/Application/WebSocket/Version/MessageInterface.php @@ -1,10 +1,15 @@ <?php -namespace Ratchet\Protocol\WebSocket; +namespace Ratchet\Application\WebSocket\Version; /** * @todo Consider making parent interface/composite for Message/Frame with (isCoalesced, getOpcdoe, getPayloadLength, getPayload) */ interface MessageInterface { + /** + * @alias getPayload + */ + function __toString(); + /** * @return bool */ @@ -13,7 +18,7 @@ interface MessageInterface { /** * @param FragmentInterface */ - function addFragment(FragmentInterface $fragment); + function addFrame(FrameInterface $fragment); /** * @return int diff --git a/lib/Ratchet/Application/WebSocket/Version/VersionInterface.php b/lib/Ratchet/Application/WebSocket/Version/VersionInterface.php index d671b22..4d4df15 100644 --- a/lib/Ratchet/Application/WebSocket/Version/VersionInterface.php +++ b/lib/Ratchet/Application/WebSocket/Version/VersionInterface.php @@ -13,11 +13,22 @@ interface VersionInterface { */ function handshake($message); + /** + * @return MessageInterface + */ + function newMessage(); + + /** + * @return FrameInterface + */ + function newFrame(); + /** * Get a framed message as per the protocol and return the decoded message * @param string * @return string * @todo Return a frame object with message, type, masked? + * @deprecated */ function unframe($message); diff --git a/tests/Ratchet/Tests/Application/WebSocket/Version/HyBi10/FrameTest.php b/tests/Ratchet/Tests/Application/WebSocket/Version/HyBi10/FrameTest.php new file mode 100644 index 0000000..0dbd13b --- /dev/null +++ b/tests/Ratchet/Tests/Application/WebSocket/Version/HyBi10/FrameTest.php @@ -0,0 +1,256 @@ +<?php +namespace Ratchet\Tests\Application\WebSocket\Version\HyBi10; +use Ratchet\Application\WebSocket\Version\HyBi10\Frame; + +/** + * @covers Ratchet\Application\WebSocket\Version\HyBi10\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 \PHPUnit_Framework_TestCase { + protected $_firstByteFinText = '10000001'; + protected $_secondByteMaskedSPL = '11111101'; + + protected $_frame; + + protected $_packer; + + public function setUp() { + $this->_frame = new Frame; + } + + protected static function convert($in) { + return pack('C', 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() { + 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=') + ); + } + + /** + * 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() { + return array( + array(false, 8, '00001000') + , array(true, 10, '10001010') + , array(false, 15, '00001111') + , array(true, 1, '10000001') + , array(true, 15, '11111111') + ); + } + + public function testUnderflowExceptionFromAllTheMethodsMimickingBuffering() { + return $this->markTestIncomplete(); + + $this->expectException('\UnderflowException'); + } + + /** + * @dataProvider firstByteProvider + */ + public function testFinCodeFromBits($fin, $opcode, $bin) { + $this->_frame->addBuffer(static::convert($bin)); + $this->assertEquals($fin, $this->_frame->isFinal()); + } + + /** + * @dataProvider UnframeMessageProvider + */ + public function testFinCodeFromFullMessage($msg, $encoded) { + $this->_frame->addBuffer(base64_decode($encoded)); + $this->assertTrue($this->_frame->isFinal()); + } + + /** + * @dataProvider firstByteProvider + */ + public function testOpcodeFromBits($fin, $opcode, $bin) { + $this->_frame->addBuffer(static::convert($bin)); + $this->assertEquals($opcode, $this->_frame->getOpcode()); + } + + /** + * @dataProvider UnframeMessageProvider + */ + public function testOpcodeFromFullMessage($msg, $encoded) { + $this->_frame->addBuffer(base64_decode($encoded)); + $this->assertEquals(1, $this->_frame->getOpcode()); + } + + public static function payloadLengthDescriptionProvider() { + 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($bits, $bin) { + $this->_frame->addBuffer(static::convert($this->_firstByteFinText)); + $this->_frame->addBuffer(static::convert($bin)); + + $ref = new \ReflectionClass($this->_frame); + $cb = $ref->getMethod('getFirstPayloadVal'); + $cb->setAccessible(true); + + $this->assertEquals(bindec($bin), $cb->invoke($this->_frame)); + } + + /** + * @dataProvider payloadLengthDescriptionProvider + */ + public function testDetermineHowManyBitsAreUsedToDescribePayload($expected_bits, $bin) { + $this->_frame->addBuffer(static::convert($this->_firstByteFinText)); + $this->_frame->addBuffer(static::convert($bin)); + + $ref = new \ReflectionClass($this->_frame); + $cb = $ref->getMethod('getNumPayloadBits'); + $cb->setAccessible(true); + + $this->assertEquals($expected_bits, $cb->invoke($this->_frame)); + } + + public function secondByteProvider() { + return array( + array(true, 1, '10000001') + , array(false, 1, '00000001') + , array(true, 125, $this->_secondByteMaskedSPL) + ); + } + + /** + * @dataProvider secondByteProvider + */ + public function testIsMaskedReturnsExpectedValue($masked, $payload_length, $bin) { + $this->_frame->addBuffer(static::convert($this->_firstByteFinText)); + $this->_frame->addBuffer(static::convert($bin)); + + $this->assertEquals($masked, $this->_frame->isMasked()); + } + + /** + * @dataProvider UnframeMessageProvider + */ + public function testIsMaskedFromFullMessage($msg, $encoded) { + $this->_frame->addBuffer(base64_decode($encoded)); + $this->assertTrue($this->_frame->isMasked()); + } + + /** + * @dataProvider secondByteProvider + */ + public function testGetPayloadLengthWhenOnlyFirstFrameIsUsed($masked, $payload_length, $bin) { + $this->_frame->addBuffer(static::convert($this->_firstByteFinText)); + $this->_frame->addBuffer(static::convert($bin)); + + $this->assertEquals($payload_length, $this->_frame->getPayloadLength()); + } + + /** + * @dataProvider UnframeMessageProvider + * @todo Not yet testing when second additional payload length descriptor + */ + public function testGetPayloadLengthFromFullMessage($msg, $encoded) { + $this->_frame->addBuffer(base64_decode($encoded)); + $this->assertEquals(strlen($msg), $this->_frame->getPayloadLength()); + } + + protected function generateMask() { + } + + protected function getPacker() { + return function($str) { + $packed = ''; + for ($i = 0, $len = strlen($str); $i < $len; $i++) { + $packed .= pack('C', ord($str[$i])); +// $packed .= pack("c", ord(substr($str, $i, 1))); + } + + return $packed; + }; + } + + public function maskingKeyProvider() { + $packer = $this->getPacker(); + $mask_generator = function() use ($packer) { + $characters = 'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*() -=_+`~[]{}\|/,.<>'; + $mask = ''; + + for ($i = 0; $i < 32; $i++) { + $mask .= $characters[mt_rand(0, strlen($characters) -1)]; + } + + return array($mask, $packer($mask)); + }; + + return array( + $mask_generator() + , $mask_generator() + , $mask_generator() + ); + } + + /** + * @dataProvider maskingKeyProvider + * @todo I I wrote the dataProvider incorrectly, skpping for now + */ + public function testGetMaskingKey($mask, $bin) { + return $this->markTestIncomplete("I'm not packing the data properly in the provider, I think"); + + $this->_frame->addBuffer(static::convert($this->_firstByteFinText)); + $this->_frame->addBuffer(static::convert($this->_secondByteMaskedSPL)); + $this->_frame->addBuffer($bin); + + $this->assertEquals($mask, $this->_frame->getMaskingKey()); + } + + /** + * @dataProvider UnframeMessageProvider + * @todo Move this test to bottom as it requires all methods of the class + */ + public function testUnframeFullMessage($unframed, $base_framed) { + $this->_frame->addBuffer(base64_decode($base_framed)); + $this->assertEquals($unframed, $this->_frame->getPayload()); + } + + public static function messageFragmentProvider() { + return array( + array(false, '', '', '', '', '') + ); + } + + /** + * @dataProvider messageFragmentProvider + */ + public function testCheckPiecingTogetherMessage($coalesced, $first_bin, $secnd_bin, $mask, $payload1, $payload2) { + return $this->markTestIncomplete('Ran out of time, had to attend to something else, come finish me!'); + + $this->_frame->addBuffer(static::convert($first_bin)); + $this->_frame->addBuffer(static::convert($second_bin)); + // mask? +// $this->_frame->addBuffer( +// $this->_frame->addBuffer( + + $this->assertEquals($coalesced, $this->_frame->isCoalesced()); + } +} \ No newline at end of file