|
| 1 | +<?php |
| 2 | +/** |
| 3 | + * Created by PhpStorm. |
| 4 | + * User: Inhere |
| 5 | + * Date: 2017/4/3 0003 |
| 6 | + * Time: 01:05 |
| 7 | + */ |
| 8 | + |
| 9 | +namespace MyLib\WebSocket\Util; |
| 10 | + |
| 11 | +/** |
| 12 | + * Class WSHelper |
| 13 | + * @package MyLib\WebSocket\Util |
| 14 | + */ |
| 15 | +class Helper |
| 16 | +{ |
| 17 | + public static function encode() |
| 18 | + { |
| 19 | + |
| 20 | + } |
| 21 | + |
| 22 | + /** |
| 23 | + * @param string$s |
| 24 | + * @return string |
| 25 | + */ |
| 26 | + public static function frame(string $s): string |
| 27 | + { |
| 28 | + $a = str_split($s, 125); |
| 29 | + $prefix = WebSocketInterface::BINARY_TYPE_BLOB; |
| 30 | + |
| 31 | + if (\count($a) === 1) { |
| 32 | + return $prefix . \chr(\strlen($a[0])) . $a[0]; |
| 33 | + } |
| 34 | + |
| 35 | + $ns = ''; |
| 36 | + |
| 37 | + foreach ($a as $o) { |
| 38 | + $ns .= $prefix . \chr(\strlen($o)) . $o; |
| 39 | + } |
| 40 | + |
| 41 | + return $ns; |
| 42 | + } |
| 43 | + |
| 44 | + /** |
| 45 | + * @param $buffer |
| 46 | + * @return string |
| 47 | + */ |
| 48 | + public static function decode($buffer) |
| 49 | + { |
| 50 | + /*$len = $masks = $data =*/ |
| 51 | + $decoded = ''; |
| 52 | + $len = \ord($buffer[1]) & 127; |
| 53 | + |
| 54 | + if ($len === 126) { |
| 55 | + $masks = \substr($buffer, 4, 4); |
| 56 | + $data = \substr($buffer, 8); |
| 57 | + } else if ($len === 127) { |
| 58 | + $masks = \substr($buffer, 10, 4); |
| 59 | + $data = \substr($buffer, 14); |
| 60 | + } else { |
| 61 | + $masks = \substr($buffer, 2, 4); |
| 62 | + $data = \substr($buffer, 6); |
| 63 | + } |
| 64 | + |
| 65 | + $dataLen = \strlen($data); |
| 66 | + for ($index = 0; $index < $dataLen; $index++) { |
| 67 | + $decoded .= $data[$index] ^ $masks[$index % 4]; |
| 68 | + } |
| 69 | + |
| 70 | + return $decoded; |
| 71 | + } |
| 72 | + |
| 73 | + /** |
| 74 | + * @param string $payload |
| 75 | + * @param string $type |
| 76 | + * @param bool $masked |
| 77 | + * @return bool|string |
| 78 | + * @throws \Exception |
| 79 | + */ |
| 80 | + public static function hybi10Encode(string $payload, string $type = 'text', bool $masked = true) |
| 81 | + { |
| 82 | + $frameHead = array(); |
| 83 | + $payloadLength = \strlen($payload); |
| 84 | + |
| 85 | + switch ($type) { |
| 86 | + //文本内容 |
| 87 | + case 'text': |
| 88 | + // first byte indicates FIN, Text-Frame (10000001): |
| 89 | + $frameHead[0] = 129; |
| 90 | + break; |
| 91 | + //二进制内容 |
| 92 | + case 'binary': |
| 93 | + case 'bin': |
| 94 | + // first byte indicates FIN, Text-Frame (10000010): |
| 95 | + $frameHead[0] = 130; |
| 96 | + break; |
| 97 | + case 'close': |
| 98 | + // first byte indicates FIN, Close Frame(10001000): |
| 99 | + $frameHead[0] = 136; |
| 100 | + break; |
| 101 | + case 'ping': |
| 102 | + // first byte indicates FIN, Ping frame (10001001): |
| 103 | + $frameHead[0] = 137; |
| 104 | + break; |
| 105 | + case 'pong': |
| 106 | + // first byte indicates FIN, Pong frame (10001010): |
| 107 | + $frameHead[0] = 138; |
| 108 | + break; |
| 109 | + } |
| 110 | + |
| 111 | + // set mask and payload length (using 1, 3 or 9 bytes) |
| 112 | + if ($payloadLength > 65535) { |
| 113 | + $payloadLengthBin = str_split(sprintf('%064b', $payloadLength), 8); |
| 114 | + $frameHead[1] = ($masked === true) ? 255 : 127; |
| 115 | + |
| 116 | + for ($i = 0; $i < 8; $i++) { |
| 117 | + $frameHead[$i + 2] = bindec($payloadLengthBin[$i]); |
| 118 | + } |
| 119 | + |
| 120 | + // most significant bit MUST be 0 (close connection if frame too big) |
| 121 | + if ($frameHead[2] > 127) { |
| 122 | + // todo `$this->close()`; |
| 123 | + return false; |
| 124 | + } |
| 125 | + } elseif ($payloadLength > 125) { |
| 126 | + $payloadLengthBin = str_split(sprintf('%016b', $payloadLength), 8); |
| 127 | + $frameHead[1] = ($masked === true) ? 254 : 126; |
| 128 | + $frameHead[2] = bindec($payloadLengthBin[0]); |
| 129 | + $frameHead[3] = bindec($payloadLengthBin[1]); |
| 130 | + } else { |
| 131 | + $frameHead[1] = ($masked === true) ? $payloadLength + 128 : $payloadLength; |
| 132 | + } |
| 133 | + |
| 134 | + // convert frame-head to string: |
| 135 | + foreach ($frameHead as $i => $v) { |
| 136 | + $frameHead[$i] = \chr($frameHead[$i]); |
| 137 | + } |
| 138 | + |
| 139 | + // generate a random mask: |
| 140 | + $mask = array(); |
| 141 | + if ($masked === true) { |
| 142 | + for ($i = 0; $i < 4; $i++) { |
| 143 | + $mask[$i] = \chr(random_int(0, 255)); |
| 144 | + } |
| 145 | + |
| 146 | + $frameHead = array_merge($frameHead, $mask); |
| 147 | + } |
| 148 | + |
| 149 | + $frame = implode('', $frameHead); |
| 150 | + |
| 151 | + // append payload to frame: |
| 152 | + for ($i = 0; $i < $payloadLength; $i++) { |
| 153 | + $frame .= $masked ? $payload[$i] ^ $mask[$i % 4] : $payload[$i]; |
| 154 | + } |
| 155 | + |
| 156 | + return $frame; |
| 157 | + } |
| 158 | + |
| 159 | + /** |
| 160 | + * @param string $data |
| 161 | + * @return string |
| 162 | + * @throws \InvalidArgumentException |
| 163 | + */ |
| 164 | + public static function hybi10Decode(string $data): string |
| 165 | + { |
| 166 | + if (!$data) { |
| 167 | + throw new \InvalidArgumentException('data is empty'); |
| 168 | + } |
| 169 | + |
| 170 | + $bytes = $data; |
| 171 | + $secondByte = sprintf('%08b', \ord($bytes[1])); |
| 172 | + $masked = '1' === $secondByte[0]; |
| 173 | + $dataLength = ($masked === true) ? \ord($bytes[1]) & 127 : \ord($bytes[1]); |
| 174 | + |
| 175 | + //服务器不会设置mask |
| 176 | + if ($dataLength === 126) { |
| 177 | + $decodedData = \substr($bytes, 4); |
| 178 | + } elseif ($dataLength === 127) { |
| 179 | + $decodedData = \substr($bytes, 10); |
| 180 | + } else { |
| 181 | + $decodedData = \substr($bytes, 2); |
| 182 | + } |
| 183 | + |
| 184 | + return $decodedData; |
| 185 | + } |
| 186 | + |
| 187 | +} |
0 commit comments