Skip to content

Commit 636d20e

Browse files
committed
add some ws tools class from inhere/websocket
1 parent f70587c commit 636d20e

19 files changed

+2345
-0
lines changed

src/DataHelper.php

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
<?php
2+
/**
3+
* Created by PhpStorm.
4+
* User: inhere
5+
* Date: 2017-08-23
6+
* Time: 13:50
7+
*/
8+
9+
namespace MyLib\WebSocket\Util;
10+
11+
/**
12+
* Class DataHelper
13+
* @package MyLib\WebSocket\Util
14+
*/
15+
class DataHelper
16+
{
17+
18+
protected function frame($message, $user, $messageType = 'text', $messageContinues = false): string
19+
{
20+
switch ($messageType) {
21+
case 'continuous':
22+
$b1 = 0;
23+
break;
24+
case 'text':
25+
$b1 = ($user->sendingContinuous) ? 0 : 1;
26+
break;
27+
case 'binary':
28+
$b1 = ($user->sendingContinuous) ? 0 : 2;
29+
break;
30+
case 'close':
31+
$b1 = 8;
32+
break;
33+
case 'ping':
34+
$b1 = 9;
35+
break;
36+
case 'pong':
37+
$b1 = 10;
38+
break;
39+
default:
40+
throw new \InvalidArgumentException('Error message type value.');
41+
break;
42+
}
43+
44+
if ($messageContinues) {
45+
$user->sendingContinuous = true;
46+
} else {
47+
$b1 += 128;
48+
$user->sendingContinuous = false;
49+
}
50+
$length = \strlen($message);
51+
$lengthField = "";
52+
if ($length < 126) {
53+
$b2 = $length;
54+
} elseif ($length < 65536) {
55+
$b2 = 126;
56+
$hexLength = dechex($length);
57+
//$this->stdout("Hex Length: $hexLength");
58+
if (\strlen($hexLength) % 2 === 1) {
59+
$hexLength = '0' . $hexLength;
60+
}
61+
62+
$n = \strlen($hexLength) - 2;
63+
64+
for ($i = $n; $i >= 0; $i -= 2) {
65+
$lengthField = \chr(hexdec(substr($hexLength, $i, 2))) . $lengthField;
66+
}
67+
68+
while (\strlen($lengthField) < 2) {
69+
$lengthField = \chr(0) . $lengthField;
70+
}
71+
} else {
72+
$b2 = 127;
73+
$hexLength = dechex($length);
74+
75+
if (\strlen($hexLength) % 2 === 1) {
76+
$hexLength = '0' . $hexLength;
77+
}
78+
79+
$n = \strlen($hexLength) - 2;
80+
for ($i = $n; $i >= 0; $i -= 2) {
81+
$lengthField = \chr(hexdec(substr($hexLength, $i, 2))) . $lengthField;
82+
}
83+
84+
while (\strlen($lengthField) < 8) {
85+
$lengthField = \chr(0) . $lengthField;
86+
}
87+
}
88+
89+
return \chr($b1) . \chr($b2) . $lengthField . $message;
90+
}
91+
92+
//check packet if he have more than one frame and process each frame individually
93+
protected function split_packet($length, $packet, $user)
94+
{
95+
//add PartialPacket and calculate the new $length
96+
if ($user->handlingPartialPacket) {
97+
$packet = $user->partialBuffer . $packet;
98+
$user->handlingPartialPacket = false;
99+
$length = \strlen($packet);
100+
}
101+
102+
$fullpacket = $packet;
103+
$frame_pos = 0;
104+
$frame_id = 1;
105+
106+
while ($frame_pos < $length) {
107+
$headers = $this->extractHeaders($packet);
108+
$headers_size = $this->calcoffset($headers);
109+
$frameSize = $headers['length'] + $headers_size;
110+
111+
//split frame from packet and process it
112+
$frame = substr($fullpacket, $frame_pos, $frameSize);
113+
114+
if (($message = $this->deFrame($frame, $user, $headers)) !== FALSE) {
115+
if ($user->hasSentClose) {
116+
$this->disconnect($user->socket);
117+
} else {
118+
if ((preg_match('//u', $message)) || ($headers['opcode'] == 2)) {
119+
//$this->stdout("Text msg encoded UTF-8 or Binary msg\n".$message);
120+
$this->process($user, $message);
121+
} else {
122+
$this->stderr("not UTF-8\n");
123+
}
124+
}
125+
}
126+
127+
//get the new position also modify packet data
128+
$frame_pos += $frameSize;
129+
$packet = substr($fullpacket, $frame_pos);
130+
$frame_id++;
131+
}
132+
}
133+
134+
protected function calcoffset($headers): int
135+
{
136+
$offset = 2;
137+
if ($headers['hasmask']) {
138+
$offset += 4;
139+
}
140+
if ($headers['length'] > 65535) {
141+
$offset += 8;
142+
} elseif ($headers['length'] > 125) {
143+
$offset += 2;
144+
}
145+
146+
return $offset;
147+
}
148+
149+
protected function deFrame($message, &$user)
150+
{
151+
//echo $this->strToHex($message);
152+
$headers = $this->extractHeaders($message);
153+
$pongReply = $willClose = false;
154+
155+
switch ($headers['opcode']) {
156+
case 0:
157+
case 1:
158+
case 2:
159+
break;
160+
case 8:
161+
// todo: close the connection
162+
$user->hasSentClose = true;
163+
164+
return '';
165+
case 9:
166+
$pongReply = true;
167+
break;
168+
case 10:
169+
break;
170+
default:
171+
//$this->disconnect($user); // todo: fail connection
172+
$willClose = true;
173+
break;
174+
}
175+
/* Deal by split_packet() as now deFrame() do only one frame at a time.
176+
if ($user->handlingPartialPacket) {
177+
$message = $user->partialBuffer . $message;
178+
$user->handlingPartialPacket = false;
179+
return $this->deFrame($message, $user);
180+
}
181+
*/
182+
183+
if ($this->checkRSVBits($headers, $user)) {
184+
return false;
185+
}
186+
187+
if ($willClose) {
188+
// todo: fail the connection
189+
return false;
190+
}
191+
192+
$payload = $user->partialMessage . $this->extractPayload($message, $headers);
193+
194+
if ($pongReply) {
195+
$reply = $this->frame($payload, $user, 'pong');
196+
socket_write($user->socket, $reply, \strlen($reply));
197+
198+
return false;
199+
}
200+
201+
if ($headers['length'] > \strlen($this->applyMask($headers, $payload))) {
202+
$user->handlingPartialPacket = true;
203+
$user->partialBuffer = $message;
204+
205+
return false;
206+
}
207+
208+
$payload = $this->applyMask($headers, $payload);
209+
if ($headers['fin']) {
210+
$user->partialMessage = '';
211+
212+
return $payload;
213+
}
214+
215+
$user->partialMessage = $payload;
216+
217+
return false;
218+
}
219+
220+
protected function extractHeaders($message): array
221+
{
222+
$header = [
223+
'fin' => $message[0] & \chr(128),
224+
'rsv1' => $message[0] & \chr(64),
225+
'rsv2' => $message[0] & \chr(32),
226+
'rsv3' => $message[0] & \chr(16),
227+
'opcode' => \ord($message[0]) & 15,
228+
'hasmask' => $message[1] & \chr(128),
229+
'length' => 0,
230+
'mask' => ''
231+
];
232+
233+
$header['length'] = (\ord($message[1]) >= 128) ? \ord($message[1]) - 128 : \ord($message[1]);
234+
235+
if ($header['length'] === 126) {
236+
if ($header['hasmask']) {
237+
$header['mask'] = $message[4] . $message[5] . $message[6] . $message[7];
238+
}
239+
240+
$header['length'] = \ord($message[2]) * 256 + \ord($message[3]);
241+
} elseif ($header['length'] === 127) {
242+
243+
if ($header['hasmask']) {
244+
$header['mask'] = $message[10] . $message[11] . $message[12] . $message[13];
245+
}
246+
247+
$header['length'] = \ord($message[2]) * 65536 * 65536 * 65536 * 256
248+
+ \ord($message[3]) * 65536 * 65536 * 65536
249+
+ \ord($message[4]) * 65536 * 65536 * 256
250+
+ \ord($message[5]) * 65536 * 65536
251+
+ \ord($message[6]) * 65536 * 256
252+
+ \ord($message[7]) * 65536
253+
+ \ord($message[8]) * 256
254+
+ \ord($message[9]);
255+
256+
} elseif ($header['hasmask']) {
257+
$header['mask'] = $message[2] . $message[3] . $message[4] . $message[5];
258+
}
259+
260+
//echo $this->strToHex($message);
261+
//$this->printHeaders($header);
262+
263+
return $header;
264+
}
265+
266+
protected function extractPayload($message, $headers)
267+
{
268+
$offset = 2;
269+
if ($headers['hasmask']) {
270+
$offset += 4;
271+
}
272+
273+
if ($headers['length'] > 65535) {
274+
$offset += 8;
275+
} elseif ($headers['length'] > 125) {
276+
$offset += 2;
277+
}
278+
279+
return substr($message, $offset);
280+
}
281+
282+
protected function applyMask($headers, $payload): int
283+
{
284+
$effectiveMask = '';
285+
if ($headers['hasmask']) {
286+
$mask = $headers['mask'];
287+
} else {
288+
return $payload;
289+
}
290+
291+
while (\strlen($effectiveMask) < \strlen($payload)) {
292+
$effectiveMask .= $mask;
293+
}
294+
295+
while (\strlen($effectiveMask) > \strlen($payload)) {
296+
$effectiveMask = substr($effectiveMask, 0, -1);
297+
}
298+
299+
return $effectiveMask ^ $payload;
300+
}
301+
302+
protected function checkRSVBits($headers, $user): bool
303+
{
304+
// override this method if you are using an extension where the RSV bits are used.
305+
if (\ord($headers['rsv1']) + \ord($headers['rsv2']) + \ord($headers['rsv3']) > 0) {
306+
//$this->disconnect($user); // todo: fail connection
307+
return true;
308+
}
309+
310+
return false;
311+
}
312+
313+
protected function strToHex($str): string
314+
{
315+
$len = \strlen($str);
316+
$strOut = '';
317+
318+
for ($i = 0; $i < $len; $i++) {
319+
$strOut .= (\ord($str[$i]) < 16) ? '0' . dechex(\ord($str[$i])) : dechex(\ord($str[$i]));
320+
$strOut .= ' ';
321+
$remainder = $i % 32;
322+
323+
if ($remainder === 7) {
324+
$strOut .= ': ';
325+
}
326+
327+
if ($remainder === 15) {
328+
$strOut .= ': ';
329+
}
330+
if ($remainder === 23) {
331+
$strOut .= ': ';
332+
}
333+
if ($remainder === 31) {
334+
$strOut .= "\n";
335+
}
336+
}
337+
338+
return $strOut . "\n";
339+
}
340+
}

src/Exception/BadRequestException.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
/**
3+
* Created by PhpStorm.
4+
* User: inhere
5+
* Date: 2017-03-27
6+
* Time: 9:14
7+
*/
8+
9+
namespace MyLib\WebSocket\Util\Exception;
10+
11+
use MyLib\WebSocket\Util\Protocol\Protocol;
12+
13+
/**
14+
* Class BadRequestException
15+
* @package MyLib\WebSocket\Util\Exception
16+
*/
17+
class BadRequestException extends \Exception
18+
{
19+
/**
20+
* @param string $message
21+
* @param int $code
22+
* @param \Exception $previous
23+
*/
24+
public function __construct($message = null, $code = null, $previous = null)
25+
{
26+
if ($code === null) {
27+
$code = Protocol::HTTP_BAD_REQUEST;
28+
}
29+
parent::__construct($message, $code, $previous);
30+
}
31+
}

0 commit comments

Comments
 (0)