Hoa central
Rfc6455.php
Go to the documentation of this file.
1 <?php
2 
37 namespace Hoa\Websocket\Protocol;
38 
39 use Hoa\Http;
40 use Hoa\Websocket;
41 
50 class Rfc6455 extends Generic
51 {
57  const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
58 
59 
60 
68  public function doHandshake(Http\Request $request)
69  {
70  if (!isset($request['sec-websocket-key'])) {
72  'Bad protocol implementation: it is not RFC6455.',
73  0
74  );
75  }
76 
77  $key = $request['sec-websocket-key'];
78 
79  if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $key) ||
80  16 !== strlen(base64_decode($key))) {
82  'Header Sec-WebSocket-Key: %s is illegal.',
83  1,
84  $key
85  );
86  }
87 
88  $response = base64_encode(sha1($key . static::GUID, true));
89 
97  $this->_connection->writeAll(
98  'HTTP/1.1 101 Switching Protocols' . "\r\n" .
99  'Upgrade: websocket' . "\r\n" .
100  'Connection: Upgrade' . "\r\n" .
101  'Sec-WebSocket-Accept: ' . $response . "\r\n" .
102  'Sec-WebSocket-Version: 13' . "\r\n\r\n"
103  );
104  $this->_connection->getCurrentNode()->setHandshake(SUCCEED);
105 
106  return;
107  }
108 
115  public function readFrame()
116  {
117  $out = [];
118  $read = $this->_connection->read(1);
119 
120  if (empty($read)) {
122 
123  return $out;
124  }
125 
126  $handle = ord($read);
127  $out['fin'] = ($handle >> 7) & 0x1;
128  $out['rsv1'] = ($handle >> 6) & 0x1;
129  $out['rsv2'] = ($handle >> 5) & 0x1;
130  $out['rsv3'] = ($handle >> 4) & 0x1;
131  $out['opcode'] = $handle & 0xf;
132 
133  $handle = ord($this->_connection->read(1));
134  $out['mask'] = ($handle >> 7) & 0x1;
135  $out['length'] = $handle & 0x7f;
136  $length = &$out['length'];
137 
138  if (0x0 !== $out['rsv1'] || 0x0 !== $out['rsv2'] || 0x0 !== $out['rsv3']) {
139  $exception = new Websocket\Exception\CloseError(
140  'Get rsv1: %s, rsv2: %s, rsv3: %s, they all must be equal to 0.',
141  2,
142  [$out['rsv1'], $out['rsv2'], $out['rsv3']]
143  );
144  $exception->setErrorCode(
146  );
147 
148  throw $exception;
149  }
150 
151  if (0 === $length) {
152  $out['message'] = '';
153 
154  return $out;
155  } elseif (0x7e === $length) {
156  $handle = unpack('nl', $this->_connection->read(2));
157  $length = $handle['l'];
158  } elseif (0x7f === $length) {
159  $handle = unpack('N*l', $this->_connection->read(8));
160  $length = $handle['l2'];
161 
162  if ($length > 0x7fffffffffffffff) {
163  $exception = new Websocket\Exception\CloseError(
164  'Message is too long.',
165  3
166  );
167  $exception->setErrorCode(
169  );
170 
171  throw $exception;
172  }
173  }
174 
175  if (0x0 === $out['mask']) {
176  $out['message'] = $this->_connection->read($length);
177 
178  return $out;
179  }
180 
181  $maskN = array_map('ord', str_split($this->_connection->read(4)));
182  $maskC = 0;
183 
184  if (4 !== count($maskN)) {
185  $exception = new Websocket\Exception\CloseError(
186  'Mask is not well-formed (too short).',
187  4
188  );
189  $exception->setErrorCode(
191  );
192 
193  throw $exception;
194  }
195 
196  $buffer = 0;
197  $bufferLength = 3000;
198  $message = null;
199 
200  for ($i = 0; $i < $length; $i += $bufferLength) {
201  $buffer = min($bufferLength, $length - $i);
202  $handle = $this->_connection->read($buffer);
203 
204  for ($j = 0, $_length = strlen($handle); $j < $_length; ++$j) {
205  $handle[$j] = chr(ord($handle[$j]) ^ $maskN[$maskC]);
206  $maskC = ($maskC + 1) % 4;
207  }
208 
209  $message .= $handle;
210  }
211 
212  $out['message'] = $message;
213 
214  return $out;
215  }
216 
226  public function writeFrame(
227  $message,
228  $opcode = Websocket\Connection::OPCODE_TEXT_FRAME,
229  $end = true,
230  $mask = false
231  ) {
232  $fin = true === $end ? 0x1 : 0x0;
233  $rsv1 = 0x0;
234  $rsv2 = 0x0;
235  $rsv3 = 0x0;
236  $mask = true === $mask ? 0x1 : 0x0;
237  $length = strlen($message);
238  $out = chr(
239  ($fin << 7)
240  | ($rsv1 << 6)
241  | ($rsv2 << 5)
242  | ($rsv3 << 4)
243  | $opcode
244  );
245 
246  if (0xffff < $length) {
247  $out .= chr(($mask << 7) | 0x7f) . pack('NN', 0, $length);
248  } elseif (0x7d < $length) {
249  $out .= chr(($mask << 7) | 0x7e) . pack('n', $length);
250  } else {
251  $out .= chr(($mask << 7) | $length);
252  }
253 
254  if (0x0 === $mask) {
255  $out .= $message;
256  } else {
257  $maskingKey = [];
258 
259  if (function_exists('openssl_random_pseudo_bytes')) {
260  $maskingKey = array_map(
261  'ord',
262  str_split(
263  openssl_random_pseudo_bytes(4)
264  )
265  );
266  } else {
267  for ($i = 0; $i < 4; ++$i) {
268  $maskingKey[] = mt_rand(1, 255);
269  }
270  }
271 
272  for ($i = 0, $max = strlen($message); $i < $max; ++$i) {
273  $message[$i] = chr(ord($message[$i]) ^ $maskingKey[$i % 4]);
274  }
275 
276  $out .= implode('', array_map('chr', $maskingKey)) .
277  $message;
278  }
279 
280  return $this->_connection->writeAll($out);
281  }
282 
294  public function send(
295  $message,
296  $opcode = Websocket\Connection::OPCODE_TEXT_FRAME,
297  $end = true,
298  $mask = false
299  ) {
300  if ((Websocket\Connection::OPCODE_TEXT_FRAME === $opcode ||
301  Websocket\Connection::OPCODE_CONTINUATION_FRAME === $opcode) &&
302  false === (bool) preg_match('//u', $message)) {
304  'Message ā€œ%sā€ is not in UTF-8, cannot send it.',
305  5,
306  32 > strlen($message)
307  ? substr($message, 0, 32) . 'ā€¦'
308  : $message
309  );
310  }
311 
312  $this->writeFrame($message, $opcode, $end, $mask);
313 
314  return;
315  }
316 
327  public function close(
328  $code = Websocket\Connection::CLOSE_NORMAL,
329  $reason = null,
330  $mask = false
331  ) {
332  $this->writeFrame(
333  pack('n', $code) . $reason,
335  true,
336  $mask
337  );
338 
339  return;
340  }
341 }
writeFrame($message, $opcode=Websocket\Connection::OPCODE_TEXT_FRAME, $end=true, $mask=false)
Definition: Rfc6455.php:226
doHandshake(Http\Request $request)
Definition: Rfc6455.php:68
close($code=Websocket\Connection::CLOSE_NORMAL, $reason=null, $mask=false)
Definition: Rfc6455.php:327
send($message, $opcode=Websocket\Connection::OPCODE_TEXT_FRAME, $end=true, $mask=false)
Definition: Rfc6455.php:294