Moved frame logic to WebSocketCodec
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,2 +1,3 @@
 | 
			
		||||
/vendor/
 | 
			
		||||
/composer.lock
 | 
			
		||||
/.phpunit.*
 | 
			
		||||
 
 | 
			
		||||
@@ -16,5 +16,8 @@
 | 
			
		||||
    ],
 | 
			
		||||
    "require": {
 | 
			
		||||
        "react/http": "^1.9.0"
 | 
			
		||||
    },
 | 
			
		||||
    "require-dev": {
 | 
			
		||||
        "phpunit/phpunit": "^11.0"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								phpunit.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								phpunit.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.0/phpunit.xsd"
 | 
			
		||||
         bootstrap="vendor/autoload.php"
 | 
			
		||||
         cacheDirectory=".phpunit.cache"
 | 
			
		||||
         executionOrder="depends,defects"
 | 
			
		||||
         requireCoverageMetadata="true"
 | 
			
		||||
         beStrictAboutCoverageMetadata="true"
 | 
			
		||||
         beStrictAboutOutputDuringTests="true"
 | 
			
		||||
         failOnRisky="true"
 | 
			
		||||
         failOnWarning="true">
 | 
			
		||||
    <testsuites>
 | 
			
		||||
        <testsuite name="default">
 | 
			
		||||
            <directory>tests</directory>
 | 
			
		||||
        </testsuite>
 | 
			
		||||
    </testsuites>
 | 
			
		||||
 | 
			
		||||
    <source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true">
 | 
			
		||||
        <include>
 | 
			
		||||
            <directory>src</directory>
 | 
			
		||||
        </include>
 | 
			
		||||
    </source>
 | 
			
		||||
</phpunit>
 | 
			
		||||
							
								
								
									
										189
									
								
								src/WebSocketCodec.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								src/WebSocketCodec.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace NoccyLabs\React\WebSocket;
 | 
			
		||||
 | 
			
		||||
use Evenement\EventEmitterTrait;
 | 
			
		||||
use NoccyLabs\React\WebSocket\Group\ConnectionGroup;
 | 
			
		||||
use NoccyLabs\React\WebSocket\Group\GroupManager;
 | 
			
		||||
use Psr\Http\Message\ServerRequestInterface;
 | 
			
		||||
use React\Http\Message\Response;
 | 
			
		||||
use React\Socket\ConnectionInterface;
 | 
			
		||||
use React\Stream\CompositeStream;
 | 
			
		||||
use React\Stream\DuplexStreamInterface;
 | 
			
		||||
use React\Stream\ReadableStreamInterface;
 | 
			
		||||
use React\Stream\ThroughStream;
 | 
			
		||||
use React\Stream\WritableStreamInterface;
 | 
			
		||||
 | 
			
		||||
class WebSocketCodec
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    const OP_CONTINUATION = 0x0;
 | 
			
		||||
    const OP_FRAME_TEXT = 0x1;
 | 
			
		||||
    const OP_FRAME_BINARY = 0x2;
 | 
			
		||||
    const OP_CLOSE = 0x8;
 | 
			
		||||
    const OP_PING = 0x9;
 | 
			
		||||
    const OP_PONG = 0xA;
 | 
			
		||||
 | 
			
		||||
    public function encode(array $frame): string
 | 
			
		||||
    {
 | 
			
		||||
        // Encoded frame
 | 
			
		||||
        $encoded = null;
 | 
			
		||||
 | 
			
		||||
        // Unpack frame with defaults
 | 
			
		||||
        $frame = [
 | 
			
		||||
            ...[
 | 
			
		||||
                'final' => true,
 | 
			
		||||
                'opcode' => null,
 | 
			
		||||
                'masked' => false,
 | 
			
		||||
                'mask' => null,
 | 
			
		||||
                'rsv1' => false,
 | 
			
		||||
                'rsv2' => false,
 | 
			
		||||
                'rsv3' => false,
 | 
			
		||||
                'payload' => null
 | 
			
		||||
            ],
 | 
			
		||||
            ...$frame
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        $len = strlen($frame['payload']);
 | 
			
		||||
        if ($len > 65535) {
 | 
			
		||||
            $size0 = 127;
 | 
			
		||||
            $size1 = ($len >> 24) & 0xFF;
 | 
			
		||||
            $size2 = ($len >> 16) & 0xFF;
 | 
			
		||||
            $size3 = ($len >> 8) & 0xFF;
 | 
			
		||||
            $size4 = $len & 0xFF;
 | 
			
		||||
        } elseif ($len > 126) {
 | 
			
		||||
            $size0 = 126;
 | 
			
		||||
            $size1 = ($len >> 8) & 0xFF;
 | 
			
		||||
            $size2 = $len & 0xFF;
 | 
			
		||||
            $size3 = null;
 | 
			
		||||
        } else {
 | 
			
		||||
            $size0 = $len;
 | 
			
		||||
            $size1 = null;
 | 
			
		||||
            $size3 = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $encoded .= chr(($frame['final']?0x80:0x00) 
 | 
			
		||||
                    | ($frame['rsv1']?0x40:0x00) 
 | 
			
		||||
                    | ($frame['rsv2']?0x20:0x00) 
 | 
			
		||||
                    | ($frame['rsv3']?0x10:0x00)
 | 
			
		||||
                    | ($frame['opcode'] & 0xF));
 | 
			
		||||
        $encoded .= chr(($frame['masked']?0x80:0x00)
 | 
			
		||||
                    | ($size0 & 0x7F));
 | 
			
		||||
        if ($size1 !== null) {
 | 
			
		||||
            $encoded .= chr($size1) . chr($size2);
 | 
			
		||||
        }
 | 
			
		||||
        if ($size3 !== null) {
 | 
			
		||||
            $encoded .= chr($size3) . chr($size4);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($frame['masked'] === true) {
 | 
			
		||||
            if ($frame['mask'] === null || strlen($frame['mask']) !== 4) {
 | 
			
		||||
                $frame['mask'] = chr(mt_rand(0,255)).chr(mt_rand(0,255)).chr(mt_rand(0,255)).chr(mt_rand(0,255));
 | 
			
		||||
            }
 | 
			
		||||
            $encoded .= $frame['mask'];
 | 
			
		||||
            $encoded .= $this->mask($frame['payload'], $frame['mask']);
 | 
			
		||||
        } else {
 | 
			
		||||
            $encoded .= $frame['payload'];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //$this->hexdump($encoded);
 | 
			
		||||
 | 
			
		||||
        return $encoded;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Decode a websocket frame and return an array with the keys:
 | 
			
		||||
     *   opcode (int) - the opcode
 | 
			
		||||
     *   fin (bool) - final frame
 | 
			
		||||
     *   rsv1-3 (bool) - reserved bits
 | 
			
		||||
     *   masked (bool) - if the frame was masked
 | 
			
		||||
     *   length (int) - length of payload
 | 
			
		||||
     *   payload (string) - the payload
 | 
			
		||||
     */
 | 
			
		||||
    public function decode($frame): array
 | 
			
		||||
    {
 | 
			
		||||
        // Decoded frame
 | 
			
		||||
        $decoded = [];
 | 
			
		||||
 | 
			
		||||
        // Keep track of the number of bytes in the header
 | 
			
		||||
        $header = 2;
 | 
			
		||||
 | 
			
		||||
        // Peek at the first byte, holding flags and opcode
 | 
			
		||||
        $byte0 = ord($frame[0]);
 | 
			
		||||
        $decoded['final'] = !!($byte0 & 0x80);
 | 
			
		||||
        $decoded['opcode'] = $byte0 & 0x0F;
 | 
			
		||||
        // Peek at the second byte, holding mask bit and len
 | 
			
		||||
        $byte1 = ord($frame[1]);
 | 
			
		||||
        $decoded['masked'] = $masked = !!($byte1 & 0x80);
 | 
			
		||||
        $decoded['rsv1'] = !!($byte1 & 0x40);
 | 
			
		||||
        $decoded['rsv2'] = !!($byte1 & 0x20);
 | 
			
		||||
        $decoded['rsv3'] = !!($byte1 & 0x10);
 | 
			
		||||
        $len = $byte1 & 0x7F;
 | 
			
		||||
 | 
			
		||||
        // Read extended length if present
 | 
			
		||||
        if ($len == 126) {
 | 
			
		||||
            $len = (ord($frame[$header+0]) << 8) 
 | 
			
		||||
                 | (ord($frame[$header+1]));
 | 
			
		||||
            $header += 2;
 | 
			
		||||
        } elseif ($len == 127) {
 | 
			
		||||
            $len = (ord($frame[$header+0]) << 24) 
 | 
			
		||||
                 | (ord($frame[$header+1]) << 16) 
 | 
			
		||||
                 | (ord($frame[$header+2]) << 8) 
 | 
			
		||||
                 | (ord($frame[$header+3]));
 | 
			
		||||
            $header += 4;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Now for the masking
 | 
			
		||||
        if ($masked) {
 | 
			
		||||
            $mask = substr($frame, $header, 4);
 | 
			
		||||
            $header += 4;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Extract and unmask payload
 | 
			
		||||
        $payload = substr($frame, $header, $len);
 | 
			
		||||
        if ($masked) {
 | 
			
		||||
            $payload = $this->mask($payload, $mask);
 | 
			
		||||
            $decoded['mask'] = $mask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $decoded['length'] = $len;
 | 
			
		||||
        $decoded['payload'] = $payload;
 | 
			
		||||
 | 
			
		||||
        return $decoded;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function mask(string $payload, string $mask): string
 | 
			
		||||
    {
 | 
			
		||||
        $payloadData = array_map("ord", str_split($payload,1));
 | 
			
		||||
        $maskData = array_map("ord", str_split($mask,1));
 | 
			
		||||
 | 
			
		||||
        //printf("Mask: %02x %02x %02x %02x\n", ...$maskData);
 | 
			
		||||
 | 
			
		||||
        $unmasked = [];
 | 
			
		||||
        for ($n = 0; $n < count($payloadData); $n++) {
 | 
			
		||||
            $unmasked[] = $payloadData[$n] ^ $maskData[$n % 4];
 | 
			
		||||
        }
 | 
			
		||||
        return join("", array_map("chr", $unmasked));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function hexdump($data): void
 | 
			
		||||
    {
 | 
			
		||||
        printf("%4d .\n", strlen($data));
 | 
			
		||||
        $rows = str_split($data, 16);
 | 
			
		||||
        $offs = 0;
 | 
			
		||||
        foreach ($rows as $row) {
 | 
			
		||||
            $h = []; $a = [];
 | 
			
		||||
            for ($n = 0; $n < 16; $n++) {
 | 
			
		||||
                if ($n < strlen($row)) {
 | 
			
		||||
                    $h[] = sprintf("%02x%s", ord($row[$n]), ($n==7)?"  ":" ");
 | 
			
		||||
                    $a[] = sprintf("%s%s", (ctype_print($row[$n])?$row[$n]:"."), ($n==7)?" ":"");
 | 
			
		||||
                } else {
 | 
			
		||||
                    $h[] = (($n==7)?"    ":"   ");
 | 
			
		||||
                    $a[] = (($n==7)?"  ":" ");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            printf("%04x | %s | %s\n", 16 * $offs++, join("", $h), join("", $a));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -27,6 +27,8 @@ class WebSocketConnection implements WebSocketInterface
 | 
			
		||||
 | 
			
		||||
    private ?string $groupName = null;
 | 
			
		||||
 | 
			
		||||
    private WebSocketCodec $codec;
 | 
			
		||||
 | 
			
		||||
    private ?ConnectionGroup $group = null;
 | 
			
		||||
 | 
			
		||||
    private GroupManager $groupManager;
 | 
			
		||||
@@ -43,6 +45,9 @@ class WebSocketConnection implements WebSocketInterface
 | 
			
		||||
 | 
			
		||||
    public function __construct(ServerRequestInterface $request, ReadableStreamInterface $inStream, WritableStreamInterface $outStream, GroupManager $groupManager)
 | 
			
		||||
    {
 | 
			
		||||
        // The codec is used to encode and decode frames
 | 
			
		||||
        $this->codec = new WebSocketCodec();
 | 
			
		||||
        
 | 
			
		||||
        $this->request = $request;
 | 
			
		||||
        $this->inStream = $inStream;
 | 
			
		||||
        $this->outStream = $outStream;
 | 
			
		||||
@@ -54,42 +59,10 @@ class WebSocketConnection implements WebSocketInterface
 | 
			
		||||
    private function onWebSocketData($data)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        // Keep track of the number of bytes in the header
 | 
			
		||||
        $header = 2;
 | 
			
		||||
 | 
			
		||||
        // Peek at the first byte, holding flags and opcode
 | 
			
		||||
        $byte0 = ord($data[0]);
 | 
			
		||||
        $final = !!($byte0 & 0x80);
 | 
			
		||||
        $opcode = $byte0 & 0x0F;
 | 
			
		||||
        // Peek at the second byte, holding mask bit and len
 | 
			
		||||
        $byte1 = ord($data[1]);
 | 
			
		||||
        $masked = !!($byte1 & 0x80);
 | 
			
		||||
        $len = $byte1 & 0x7F;
 | 
			
		||||
 | 
			
		||||
        // Read extended length if present
 | 
			
		||||
        if ($len == 126) {
 | 
			
		||||
            $len = (ord($data[$header+0]) << 8) 
 | 
			
		||||
                 | (ord($data[$header+1]));
 | 
			
		||||
            $header += 2;
 | 
			
		||||
        } elseif ($len == 127) {
 | 
			
		||||
            $len = (ord($data[$header+0]) << 24) 
 | 
			
		||||
                 | (ord($data[$header+1]) << 16) 
 | 
			
		||||
                 | (ord($data[$header+2]) << 8) 
 | 
			
		||||
                 | (ord($data[$header+3]));
 | 
			
		||||
            $header += 4;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Now for the masking
 | 
			
		||||
        if ($masked) {
 | 
			
		||||
            $mask = substr($data, $header, 4);
 | 
			
		||||
            $header += 4;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Extract and unmask payload
 | 
			
		||||
        $payload = substr($data, $header, $len);
 | 
			
		||||
        if ($masked) {
 | 
			
		||||
            $payload = $this->unmask($payload, $mask);
 | 
			
		||||
        }
 | 
			
		||||
        $decoded = $this->codec->decode($data);
 | 
			
		||||
        $opcode = $decoded['opcode'];
 | 
			
		||||
        $final = $decoded['final'];
 | 
			
		||||
        $payload = $decoded['payload'];
 | 
			
		||||
        
 | 
			
		||||
        if (!$final) {
 | 
			
		||||
            if ($this->bufferedOp === null) {
 | 
			
		||||
@@ -111,7 +84,12 @@ class WebSocketConnection implements WebSocketInterface
 | 
			
		||||
 | 
			
		||||
        switch ($opcode) {
 | 
			
		||||
            case self::OP_PING:
 | 
			
		||||
                $this->sendPong();
 | 
			
		||||
                $this->sendPong($payload);
 | 
			
		||||
                return;
 | 
			
		||||
            case self::OP_PONG:
 | 
			
		||||
                return;
 | 
			
		||||
            case self::OP_CLOSE:
 | 
			
		||||
                // TODO implement
 | 
			
		||||
                return;
 | 
			
		||||
            case self::OP_CONTINUATION:
 | 
			
		||||
                $this->buffer .= $payload;
 | 
			
		||||
@@ -125,24 +103,9 @@ class WebSocketConnection implements WebSocketInterface
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function unmask(string $payload, string $mask): string
 | 
			
		||||
    private function sendPong(string $data): void
 | 
			
		||||
    {
 | 
			
		||||
        $payloadData = array_map("ord", str_split($payload,1));
 | 
			
		||||
        $maskData = array_map("ord", str_split($mask,1));
 | 
			
		||||
 | 
			
		||||
        //printf("Mask: %02x %02x %02x %02x\n", ...$maskData);
 | 
			
		||||
 | 
			
		||||
        $unmasked = [];
 | 
			
		||||
        for ($n = 0; $n < count($payloadData); $n++) {
 | 
			
		||||
            $unmasked[] = $payloadData[$n] ^ $maskData[$n % 4];
 | 
			
		||||
            //printf("%02x ^ %02x = %02x %s\n", $payloadData[$n], $maskData[$n%4], $payloadData[$n]^$maskData[$n%4], chr($payloadData[$n]^$maskData[$n%4]));
 | 
			
		||||
        }
 | 
			
		||||
        return join("", array_map("chr", $unmasked));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function sendPong(): void
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        $this->send(self::OP_PONG, $data, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setGroup(?string $name): void
 | 
			
		||||
@@ -221,17 +184,15 @@ class WebSocketConnection implements WebSocketInterface
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     */
 | 
			
		||||
    public function send(int $opcode, string $data, bool $final = true)
 | 
			
		||||
    public function send(int $opcode, string $data, bool $final = true, bool $masked = false)
 | 
			
		||||
    {
 | 
			
		||||
        $frame = chr(($final?0x80:0x00) | ($opcode & 0xF));
 | 
			
		||||
        
 | 
			
		||||
        $len = strlen($data);
 | 
			
		||||
        if ($len > 126) {
 | 
			
		||||
            $frame .= chr(0x7E) . chr(($len >> 8) & 0xFF) . chr($len & 0xFF);
 | 
			
		||||
        } else {
 | 
			
		||||
            $frame .= chr($len);
 | 
			
		||||
        }
 | 
			
		||||
        $frame .= $data;
 | 
			
		||||
 | 
			
		||||
        $frame = $this->codec->encode([
 | 
			
		||||
            'opcode' => $opcode,
 | 
			
		||||
            'payload' => $data,
 | 
			
		||||
            'final' => $final,
 | 
			
		||||
            'masked' => $masked
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $this->outStream->write($frame);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										100
									
								
								tests/WebSocketCodecTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								tests/WebSocketCodecTest.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace NoccyLabs\React\WebSocket;
 | 
			
		||||
 | 
			
		||||
use PHPUnit\Framework\Attributes\CoversClass;
 | 
			
		||||
 | 
			
		||||
#[CoversClass(WebSocketCodec::class)]
 | 
			
		||||
class WebSocketCodecTest extends \PHPUnit\Framework\TestCase
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public function testEncodingFrames()
 | 
			
		||||
    {
 | 
			
		||||
        $codec = new WebSocketCodec();
 | 
			
		||||
 | 
			
		||||
        $msg = $codec->encode([
 | 
			
		||||
            'opcode'=>WebSocketCodec::OP_PING,
 | 
			
		||||
            'payload'=>"ping"
 | 
			
		||||
        ]);
 | 
			
		||||
        $this->assertEquals("\x89\x04ping", $msg);
 | 
			
		||||
 | 
			
		||||
        $msg = $codec->encode([
 | 
			
		||||
            'opcode'=>WebSocketCodec::OP_FRAME_TEXT,
 | 
			
		||||
            'payload'=>"abcdefgh"]);
 | 
			
		||||
        $this->assertEquals("\x81\x08abcdefgh", $msg);
 | 
			
		||||
 | 
			
		||||
        $msg = $codec->encode([
 | 
			
		||||
            'opcode'=>WebSocketCodec::OP_FRAME_TEXT,
 | 
			
		||||
            'payload'=>"abcdefgh",
 | 
			
		||||
            'masked'=>true,
 | 
			
		||||
            'mask'=>"\x00\x00\x00\x00"
 | 
			
		||||
        ]);
 | 
			
		||||
        $this->assertEquals("\x81\x88\x00\x00\x00\x00abcdefgh", $msg);
 | 
			
		||||
 | 
			
		||||
        $msg = $codec->encode([
 | 
			
		||||
            'opcode'=>WebSocketCodec::OP_FRAME_TEXT,
 | 
			
		||||
            'payload'=>"abcdefgh",
 | 
			
		||||
            'masked'=>true,
 | 
			
		||||
            'mask'=>"\x00\xFF\x00\xFF"
 | 
			
		||||
        ]);
 | 
			
		||||
        $this->assertEquals("\x81\x88\x00\xFF\x00\xFFa\x9dc\x9be\x99g\x97", $msg);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testDecodingFrames()
 | 
			
		||||
    {
 | 
			
		||||
        $codec = new WebSocketCodec();
 | 
			
		||||
 | 
			
		||||
        $msg = $codec->decode("\x89\x04ping");
 | 
			
		||||
        $this->assertEquals([
 | 
			
		||||
            'opcode'=>WebSocketCodec::OP_PING,
 | 
			
		||||
            'payload'=>"ping",
 | 
			
		||||
            'final'=>true,
 | 
			
		||||
            'rsv1'=>false,
 | 
			
		||||
            'rsv2'=>false,
 | 
			
		||||
            'rsv3'=>false,
 | 
			
		||||
            'length'=>4,
 | 
			
		||||
            'masked'=>false
 | 
			
		||||
        ], $msg);
 | 
			
		||||
 | 
			
		||||
        $msg = $codec->decode("\x81\x08abcdefgh");
 | 
			
		||||
        $this->assertEquals([
 | 
			
		||||
            'opcode'=>WebSocketCodec::OP_FRAME_TEXT,
 | 
			
		||||
            'payload'=>"abcdefgh",
 | 
			
		||||
            'final'=>true,
 | 
			
		||||
            'rsv1'=>false,
 | 
			
		||||
            'rsv2'=>false,
 | 
			
		||||
            'rsv3'=>false,
 | 
			
		||||
            'length'=>8,
 | 
			
		||||
            'masked'=>false
 | 
			
		||||
        ], $msg);
 | 
			
		||||
 | 
			
		||||
        $msg = $codec->decode("\x81\x88\x00\x00\x00\x00abcdefgh");
 | 
			
		||||
        $this->assertEquals([
 | 
			
		||||
            'opcode'=>WebSocketCodec::OP_FRAME_TEXT,
 | 
			
		||||
            'payload'=>"abcdefgh",
 | 
			
		||||
            'final'=>true,
 | 
			
		||||
            'rsv1'=>false,
 | 
			
		||||
            'rsv2'=>false,
 | 
			
		||||
            'rsv3'=>false,
 | 
			
		||||
            'length'=>8,
 | 
			
		||||
            'masked'=>true,
 | 
			
		||||
            'mask'=>"\x00\x00\x00\x00"
 | 
			
		||||
        ], $msg);
 | 
			
		||||
 | 
			
		||||
        $msg = $codec->decode("\x81\x88\x00\xFF\x00\xFFa\x9dc\x9be\x99g\x97");
 | 
			
		||||
        $this->assertEquals([
 | 
			
		||||
            'opcode'=>WebSocketCodec::OP_FRAME_TEXT,
 | 
			
		||||
            'payload'=>"abcdefgh",
 | 
			
		||||
            'final'=>true,
 | 
			
		||||
            'rsv1'=>false,
 | 
			
		||||
            'rsv2'=>false,
 | 
			
		||||
            'rsv3'=>false,
 | 
			
		||||
            'length'=>8,
 | 
			
		||||
            'masked'=>true,
 | 
			
		||||
            'mask'=>"\x00\xFF\x00\xFF"
 | 
			
		||||
        ], $msg);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user