From 5363d2c642f3a9b360794a64da87f20670656f4d Mon Sep 17 00:00:00 2001 From: Michael Calcinai Date: Fri, 16 Oct 2015 14:57:13 +1300 Subject: [PATCH] Fixed masking key and is now able to respond --- examples/client.php | 4 +- src/Client.php | 4 + src/Message.php | 4 + src/Protocol/ProtocolInterface.php | 3 +- src/Protocol/RFC6455.php | 12 ++- src/Protocol/RFC6455/Frame.php | 118 +++++++++++++++++++++++++---- 6 files changed, 125 insertions(+), 20 deletions(-) diff --git a/examples/client.php b/examples/client.php index 6ebece6..3eebb00 100644 --- a/examples/client.php +++ b/examples/client.php @@ -15,8 +15,8 @@ echo "State changed to: $newState\n"; }); -$client->on('message', function($message){ - print_r($message); +$client->on('message', function($message) use ($client){ + }); $loop->run(); diff --git a/src/Client.php b/src/Client.php index 82a53cb..796b9cc 100644 --- a/src/Client.php +++ b/src/Client.php @@ -144,4 +144,8 @@ public function setOrigin($origin) { return $this; } + public function send($string) { + $this->transport->send($string); + } + } \ No newline at end of file diff --git a/src/Message.php b/src/Message.php index c710172..8058457 100644 --- a/src/Message.php +++ b/src/Message.php @@ -21,6 +21,10 @@ public function addBody($body) { return $this; } + public function getBody() { + return $this->body; + } + public function isComplete() { return $this->is_complete; } diff --git a/src/Protocol/ProtocolInterface.php b/src/Protocol/ProtocolInterface.php index bab2f50..f94a85d 100644 --- a/src/Protocol/ProtocolInterface.php +++ b/src/Protocol/ProtocolInterface.php @@ -8,7 +8,8 @@ namespace Calcinai\Bolt\Protocol; interface ProtocolInterface { - public function upgrade(); public function onStreamData(&$buffer); + public function upgrade(); + public function send($string); public static function getVersion(); } \ No newline at end of file diff --git a/src/Protocol/RFC6455.php b/src/Protocol/RFC6455.php index bcd3f76..d4a4419 100644 --- a/src/Protocol/RFC6455.php +++ b/src/Protocol/RFC6455.php @@ -98,7 +98,7 @@ private function addFragmentToMessage(Frame $frame) { ->setIsComplete($frame->isFinalFragment()); if($this->current_message->isComplete()){ - $this->client->emit('message', [$this->current_message]); + $this->client->emit('message', [$this->current_message->getBody()]); unset($this->current_message); } } @@ -137,7 +137,6 @@ private function processUpgrade(Response $response) { } - public function getExpectedAcceptKey(){ if(!isset($this->websocket_key)){ //todo handle @@ -145,6 +144,14 @@ public function getExpectedAcceptKey(){ return base64_encode(pack('H*', sha1($this->websocket_key . self::WEBSOCKET_GUID))); } + + public function send($string, $type = Frame::OP_TEXT) { + $frame = new Frame($string, $type); + $this->stream->write($frame->encode()); + } + + + protected static function generateKey() { static $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$&/()=[]{}0123456789'; $key = ''; @@ -159,5 +166,4 @@ public static function getVersion() { return 10; } - } \ No newline at end of file diff --git a/src/Protocol/RFC6455/Frame.php b/src/Protocol/RFC6455/Frame.php index 2a489b0..5032dab 100644 --- a/src/Protocol/RFC6455/Frame.php +++ b/src/Protocol/RFC6455/Frame.php @@ -41,9 +41,28 @@ class Frame { const FRAME_BITS_MASKED = 1; const FRAME_BITS_LENGTH = 7; + const EXT_PAYLOAD_BITS_S = 16; + const EXT_PAYLOAD_BITS_L = 64; + + public function __construct($payload = null, $opcode = null, $fin = true){ - public function __construct(){ $this->meta_decoded = false; + + if($payload === null || $opcode === null){ + return; + } + + $this->frame_fin = $fin ? 0b1 : 0b0; + $this->frame_rsv1 = 0b0; + $this->frame_rsv2 = 0b0; + $this->frame_rsv3 = 0b0; + $this->frame_opcode = $opcode; + $this->frame_masked = 0b0; + $this->frame_length = strlen($payload); + + $this->payload = $payload; + + } public function isMasked() { @@ -63,6 +82,52 @@ public function getPayload() { return $this->payload; } + + public function encode($masked = true){ + + $this->frame_masked = $masked ? 0b1 : 0b0; + + $control = 0; + self::addBits($control, self::FRAME_BITS_FIN, $this->frame_fin); + self::addBits($control, self::FRAME_BITS_RSV1, $this->frame_rsv1); //Reserved + self::addBits($control, self::FRAME_BITS_RSV2, $this->frame_rsv2); //Reserved + self::addBits($control, self::FRAME_BITS_RSV3, $this->frame_rsv3); //Reserved + self::addBits($control, self::FRAME_BITS_OPCODE, $this->frame_opcode); + self::addBits($control, self::FRAME_BITS_MASKED, $this->frame_masked); + + //Work out how many extended bits need to be added based on payload size + if ($this->frame_length > pow(2, self::EXT_PAYLOAD_BITS_S) -1) { + // >16 bit - 8 bytes size + self::addBits($control, self::FRAME_BITS_LENGTH, 127); + + $low = $this->frame_length & pow(2, 32) - 1; + $high = $this->frame_length >> 32; + $packed = pack('nNN', $control, $high, $low); + + } elseif($this->frame_length > 125) { + // 2 bytes size + self::addBits($control, self::FRAME_BITS_LENGTH, 126); + + $packed = pack('nn', $control, $this->frame_length); + } else { + // standard size frame + self::addBits($control, self::FRAME_BITS_LENGTH, $this->frame_length); + $packed = pack('n', $control); + } + + if($this->isMasked()){ + $this->masking_key = self::generateMaskingKey(); + $packed .= pack('N', $this->masking_key); + self::mask($this->payload, $this->masking_key); + } + + $packed .= $this->payload; + + return $packed; + + } + + /** * @param $buffer * @return string @@ -79,15 +144,10 @@ public function appendBuffer(&$buffer){ public function appendPayload($buffer){ if($this->isMasked()){ - //What good does this even do!? - $mask_size = strlen($this->masking_key); - foreach(str_split($buffer, $mask_size) as $chunk){ - $this->payload .= $chunk ^ ($this->masking_key >> ($mask_size - strlen($chunk))); //That strlen catches the remainder. - } - } else { - $this->payload .= $buffer; + self::mask($buffer, $this->masking_key); } + $this->payload .= $buffer; $payload_length = strlen($this->payload); if($payload_length < $this->frame_length){ @@ -134,6 +194,17 @@ public function decode(&$buffer){ } + private function eatBytes(&$buffer, $num_bytes){ + + if(!isset($buffer[$num_bytes])){ + throw new IncompleteFrameException($this); + } + + $consumed = substr($buffer, 0, $num_bytes); + $buffer = substr($buffer, $num_bytes); + + return $consumed; + } public static function extractBits(&$data, $num_bits){ @@ -145,16 +216,35 @@ public static function extractBits(&$data, $num_bits){ return $bits; } - private function eatBytes(&$buffer, $num_bytes){ + private static function addBits(&$data, $num_bits, $bits){ - if(!isset($buffer[$num_bytes])){ - throw new IncompleteFrameException($this); + $data = $data << $num_bits; + $data |= $bits; + } + + + public static function generateMaskingKey() { + $mask = 0; + + for ($i = 0; $i < 32; $i += 8) { + $mask |= rand(32, 255) << $i; } - $consumed = substr($buffer, 0, $num_bytes); - $buffer = substr($buffer, $num_bytes); + return $mask; + } - return $consumed; + + private static function mask(&$data, $masking_key) { + + $data_size = strlen($data); + + for($i = 0; $i < $data_size; $i++) { + $remainder = 3 - $i % 4; //work backward through the masking key + $shift_bits = $remainder * 8; //Make the shift a whole byte + $xor = $masking_key >> $shift_bits; + $data[$i] = $data[$i] ^ chr($xor); + } } + } \ No newline at end of file