 7597a8c00a
			
		
	
	
		7597a8c00a
		
	
	
	
		
			
	
		
	
	
		
			Some checks are pending
		
		
	
	CI / PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} (client, ubuntu-22.04, 7.4) (push) Waiting to run
				
			CI / PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} (client, ubuntu-22.04, 8) (push) Waiting to run
				
			CI / PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} (client, ubuntu-22.04, 8.1) (push) Waiting to run
				
			CI / PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} (client, ubuntu-22.04, 8.2) (push) Waiting to run
				
			CI / PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} (client, ubuntu-22.04, 8.3) (push) Waiting to run
				
			CI / PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} (client, ubuntu-22.04, 8.4) (push) Waiting to run
				
			CI / PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} (server, ubuntu-22.04, 7.4) (push) Waiting to run
				
			CI / PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} (server, ubuntu-22.04, 8) (push) Waiting to run
				
			CI / PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} (server, ubuntu-22.04, 8.1) (push) Waiting to run
				
			CI / PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} (server, ubuntu-22.04, 8.2) (push) Waiting to run
				
			CI / PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} (server, ubuntu-22.04, 8.3) (push) Waiting to run
				
			CI / PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} (server, ubuntu-22.04, 8.4) (push) Waiting to run
				
			
		
			
				
	
	
		
			432 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			432 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| namespace mfmdevsystem\RFC6455\Test\Unit\Messaging;
 | |
| 
 | |
| use mfmdevsystem\RFC6455\Messaging\Frame;
 | |
| use PHPUnit\Framework\TestCase;
 | |
| 
 | |
| /**
 | |
|  * @covers mfmdevsystem\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());
 | |
|     }
 | |
| }
 |