PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/Quadro.class.php

https://github.com/sitegui/websocket
PHP | 275 lines | 216 code | 28 blank | 31 comment | 63 complexity | 4decb9bb29c35d873dd62467e3b0e298 MD5 | raw file
  1. <?php
  2. // Representa um quadro
  3. // As funções não levam em consideração extensões, então, por exemplo, a fragmentação é tratada da forma básica
  4. class Quadro {
  5. // Constantes para opcode
  6. const CONTINUATION = 0;
  7. const TEXT = 1;
  8. const BINARY = 2;
  9. const CLOSE = 8;
  10. const PING = 9;
  11. const PONG = 10;
  12. private $conexao; // Indica a conexão com a qual o quadro está vinculado
  13. private $fin = true; // Indica se é o quadro final (boolean)
  14. private $rsv1 = false; // 1º bit reservado para extensões (boolean)
  15. private $rsv2 = false; // 2º bit reservado para extensões (boolean)
  16. private $rsv3 = false; // 3º bit reservado para extensões (boolean)
  17. private $opcode = Quadro::TEXT; // Indica o tipo do quadro (4 bits)
  18. private $mask = false; // Indica se o conteúdo está codificado (boolean)
  19. private $length = 0; // Indica o tamanho do dado
  20. private $maskKey; // Indica a máscara de 4 bytes (string) (inicializado randomicamente)
  21. private $data = ''; // Guarda os dados decodificados (string)
  22. private $code = 1005; // Indica o código do motivo pelo qual a conexão foi/será fechada (2 bytes)
  23. private $buffer = ''; // Cache para acelerar o envio do mesmo quadro para várias conexões
  24. // Constroi um novo quadro vazio ou com base em uma string
  25. // A parte processada será removida da string
  26. // Se $forcarMascara for true, retorna um erro caso o quadro não seja codificado
  27. public function __construct(Conexao $conexao=NULL, &$str=NULL, $forcarMascara=true) {
  28. $this->conexao = $conexao;
  29. $this->setMaskKey();
  30. if ($str === NULL)
  31. // Quadro vazio
  32. return;
  33. $usado = 0;
  34. $str = (string)$str;
  35. $strlen = strlen($str);
  36. if ($strlen<2)
  37. throw new LengthException('Fim inesperado');
  38. // Primeiro byte
  39. $b1 = ord($str[0]);
  40. $this->opcode = $b1%0x10;
  41. $this->rsv3 = (boolean)(($b1>>4)%2);
  42. $this->rsv2 = (boolean)(($b1>>5)%2);
  43. $this->rsv1 = (boolean)(($b1>>6)%2);
  44. $this->fin = (boolean)($b1>>7);
  45. if (!$this->fin && $this->opcode > 7)
  46. throw new Exception('Não é permitido fragmentar quadros de controle');
  47. // Segundo byte
  48. $b2 = ord($str[1]);
  49. $iniLength = $b2%0x80;
  50. $this->mask = (boolean)($b2>>7);
  51. if ($forcarMascara && !$this->mask)
  52. throw new Exception('Quadro sem máscara inesperado');
  53. // Interpreta o tamanho
  54. $i = 2;
  55. $this->length = 0;
  56. if ($iniLength < 126)
  57. $this->length = $iniLength;
  58. else
  59. for ($max = $iniLength==126 ? 4 : 10; $i<$max; $i++) {
  60. if ($i >= $strlen-$usado)
  61. throw new LengthException('Fim inesperado');
  62. $b = ord($str[$i]);
  63. $this->length *= 0x100;
  64. $this->length += $b;
  65. }
  66. $usado = $i;
  67. // Valida o tamanho
  68. if ($this->opcode > 7 && $this->length > 125)
  69. throw new Exception('Quadro de controle muito grande');
  70. // Interpreta a máscara
  71. if ($this->mask)
  72. if ($strlen-$usado < 4)
  73. throw new LengthException('Fim inesperado');
  74. else {
  75. $this->maskKey = substr($str, $usado, 4);
  76. $usado += 4;
  77. }
  78. // O restante são os dados
  79. if ($strlen-$usado < $this->length)
  80. throw new LengthException('Fim inesperado');
  81. // Decodifica os dados
  82. $this->data = substr($str, $usado, $this->length);
  83. $usado += $this->length;
  84. if ($this->mask)
  85. for ($i=0; $i<$this->length; $i++)
  86. $this->data[$i] = $this->data[$i]^$this->maskKey[$i%4];
  87. // Interpreta o código de fechamento
  88. if ($this->opcode == Quadro::CLOSE && $this->length) {
  89. if ($this->length < 2)
  90. throw new Exception('Código de fechamento incompleto');
  91. $this->code = ord($this->data[0])*0x100+ord($this->data[1]);
  92. $this->data = substr($this->data, 2);
  93. $this->length -= 2;
  94. }
  95. $str = $usado==$strlen ? '' : substr($str, $usado);
  96. }
  97. // Junta a continuação desse fragmento nesse
  98. public function juntar(Quadro $outro) {
  99. // Verifica se a operação é válida
  100. if ($this->fin || $outro->opcode != Quadro::CONTINUATION)
  101. throw new Exception('Operação inválida');
  102. $this->fin = $outro->fin;
  103. $this->data .= $outro->data;
  104. $this->length = strlen($this->data);
  105. $this->buffer = '';
  106. }
  107. // Separa esse fragmento no número de fragmentos desejado
  108. // Os bits relacionados a extensões serão replicados para os fragmentos
  109. // Retorna uma array com esses fragmentos
  110. public function separar($num=2) {
  111. $num = (int)$num;
  112. // Verifica se a operação é válida
  113. if (!$this->fin || $this->opcode > 7 || $num<1)
  114. throw new Exception('Operação inválida');
  115. $len = ceil($this->length/$num);
  116. $frames = array();
  117. for ($i=0; $i<$num; $i++) {
  118. $frame = new Quadro;
  119. $frame->fin = $i==$num-1 ? true : false;
  120. $frame->rsv1 = $this->rsv1;
  121. $frame->rsv2 = $this->rsv2;
  122. $frame->rsv3 = $this->rsv3;
  123. $frame->opcode = $i==0 ? $this->opcode : Quadro::CONTINUATION;
  124. $frame->mask = $this->mask;
  125. $frame->maskKey = $this->maskKey;
  126. $frame->data = $i*$len < $this->length ? substr($this->data, $i*$len, $len) : '';
  127. $frame->length = strlen($frame->data);
  128. $frames[] = $frame;
  129. }
  130. return $frames;
  131. }
  132. // Retorna a representação do frame em string para ser enviado
  133. public function getString() {
  134. // Tenta pegar do buffer
  135. if ($this->buffer)
  136. return $this->buffer;
  137. // Primeiro byte
  138. $b1 = $this->fin*0x80+$this->rsv1*0x40+$this->rsv2*0x20+$this->rsv3*0x10+$this->opcode;
  139. // Coloca o código de fechamento
  140. $data = $this->data;
  141. if ($this->opcode == Quadro::CLOSE)
  142. if ($this->code == 1005)
  143. $data = '';
  144. else
  145. $data = chr($this->code>>8) . chr($this->code%0x100) . $data;
  146. // Segundo byte e tamanho
  147. $b2 = $this->mask*0x80;
  148. $str = '';
  149. $length = strlen($data);
  150. if ($length < 126)
  151. $b2 += $length;
  152. else if ($length < 0x10000) {
  153. $b2 += 126;
  154. $str = chr($length>>8) . chr($length%0x100);
  155. } else {
  156. $b2 += 127;
  157. $str = '';
  158. for ($i=0; $i<8; $i++) {
  159. $str .= chr($length%0x100);
  160. $length = floor($length/0x100);
  161. }
  162. $str = strrev($str);
  163. }
  164. $str = chr($b1) . chr($b2) . $str;
  165. // Máscara e dados
  166. if ($this->mask) {
  167. $str .= $this->maskKey;
  168. for ($i=0, $strlen = strlen($data); $i<$strlen; $i++)
  169. $data[$i] = $data[$i]^$this->maskKey[$i%4];
  170. }
  171. $str .= $data;
  172. $this->buffer = $str;
  173. return $str;
  174. }
  175. // Envia o quadro (se estiver associado a uma conexão válida)
  176. public function enviar() {
  177. if ($this->conexao && $this->conexao->getEstado() == Conexao::ABERTO)
  178. $this->conexao->enviarQuadro($this);
  179. else
  180. throw new Exception('Conexão inválida');
  181. }
  182. // Setters
  183. public function setConexao(Conexao $novo) {$this->conexao = $novo;}
  184. public function setFin($novo) {
  185. $novo = (boolean)$novo;
  186. if (!$novo && $this->opcode > 7)
  187. throw new Exception('Não é permitido fragmentar quadros de controle');
  188. $this->fin = $novo;
  189. $this->buffer = '';
  190. }
  191. public function setRsv1($novo) {$this->rsv1 = (boolean)$novo;$this->buffer = '';}
  192. public function setRsv2($novo) {$this->rsv2 = (boolean)$novo;$this->buffer = '';}
  193. public function setRsv3($novo) {$this->rsv3 = (boolean)$novo;$this->buffer = '';}
  194. public function setOpcode($novo) {
  195. $novo = (int)$novo;
  196. if ($novo<0x0 || $novo>0xF)
  197. throw new Exception('O valor de opcode deve ter 4 bits (0x0-0xF)');
  198. if (!$this->fin && $novo > 7)
  199. throw new Exception('Não é permitido fragmentar quadros de controle');
  200. $this->opcode = $novo;
  201. $this->buffer = '';
  202. }
  203. public function setMask($novo) {$this->mask = (boolean)$novo;$this->buffer = '';}
  204. // Se $novo for NULL pega um valor aleatório
  205. public function setMaskKey($novo=NULL) {
  206. if ($novo === NULL) {
  207. $novo = '';
  208. for ($i=0; $i<4; $i++)
  209. $novo .= chr(mt_rand(0, 255));
  210. } else {
  211. $novo = (string)$novo;
  212. if (strlen($novo) != 4)
  213. throw new Exception('O valor de maskKey deve ter 4 bytes');
  214. }
  215. $this->maskKey = $novo;
  216. $this->buffer = '';
  217. }
  218. public function setData($novo) {
  219. $novo = (string)$novo;
  220. if ($this->opcode > 7 && strlen($novo) > 123)
  221. throw new Exception('Dado muito grande para um quadro de controle');
  222. $this->data = $novo;
  223. $this->length = strlen($novo);
  224. $this->buffer = '';
  225. }
  226. public function setCode($novo) {
  227. $novo = (int)$novo;
  228. if ($novo<0x0 || $novo>0xFFFF)
  229. throw new Exception('O valor de code deve ter 2 bytes');
  230. else if ($novo == 1006 || $novo == 1015)
  231. throw new Exception('Código inválido');
  232. $this->code = $novo;
  233. $this->buffer = '';
  234. }
  235. // Getters
  236. public function getConexao() {return $this->conexao;}
  237. public function getFin() {return $this->fin;}
  238. public function getRsv1() {return $this->rsv1;}
  239. public function getRsv2() {return $this->rsv2;}
  240. public function getRsv3() {return $this->rsv3;}
  241. public function getOpcode() {return $this->opcode;}
  242. public function getMask() {return $this->mask;}
  243. public function getLength() {return $this->length;}
  244. public function getMaskKey() {return $this->maskKey;}
  245. public function getData() {return $this->data;}
  246. public function getCode() {return $this->code;}
  247. }