/Quadro.class.php
PHP | 275 lines | 216 code | 28 blank | 31 comment | 63 complexity | 4decb9bb29c35d873dd62467e3b0e298 MD5 | raw file
- <?php
- // Representa um quadro
- // As funções não levam em consideração extensões, então, por exemplo, a fragmentação é tratada da forma básica
- class Quadro {
- // Constantes para opcode
- const CONTINUATION = 0;
- const TEXT = 1;
- const BINARY = 2;
- const CLOSE = 8;
- const PING = 9;
- const PONG = 10;
-
- private $conexao; // Indica a conexão com a qual o quadro está vinculado
- private $fin = true; // Indica se é o quadro final (boolean)
- private $rsv1 = false; // 1º bit reservado para extensões (boolean)
- private $rsv2 = false; // 2º bit reservado para extensões (boolean)
- private $rsv3 = false; // 3º bit reservado para extensões (boolean)
- private $opcode = Quadro::TEXT; // Indica o tipo do quadro (4 bits)
- private $mask = false; // Indica se o conteúdo está codificado (boolean)
- private $length = 0; // Indica o tamanho do dado
- private $maskKey; // Indica a máscara de 4 bytes (string) (inicializado randomicamente)
- private $data = ''; // Guarda os dados decodificados (string)
- private $code = 1005; // Indica o código do motivo pelo qual a conexão foi/será fechada (2 bytes)
-
- private $buffer = ''; // Cache para acelerar o envio do mesmo quadro para várias conexões
-
- // Constroi um novo quadro vazio ou com base em uma string
- // A parte processada será removida da string
- // Se $forcarMascara for true, retorna um erro caso o quadro não seja codificado
- public function __construct(Conexao $conexao=NULL, &$str=NULL, $forcarMascara=true) {
- $this->conexao = $conexao;
- $this->setMaskKey();
- if ($str === NULL)
- // Quadro vazio
- return;
-
- $usado = 0;
- $str = (string)$str;
- $strlen = strlen($str);
- if ($strlen<2)
- throw new LengthException('Fim inesperado');
-
- // Primeiro byte
- $b1 = ord($str[0]);
- $this->opcode = $b1%0x10;
- $this->rsv3 = (boolean)(($b1>>4)%2);
- $this->rsv2 = (boolean)(($b1>>5)%2);
- $this->rsv1 = (boolean)(($b1>>6)%2);
- $this->fin = (boolean)($b1>>7);
- if (!$this->fin && $this->opcode > 7)
- throw new Exception('Não é permitido fragmentar quadros de controle');
-
- // Segundo byte
- $b2 = ord($str[1]);
- $iniLength = $b2%0x80;
- $this->mask = (boolean)($b2>>7);
- if ($forcarMascara && !$this->mask)
- throw new Exception('Quadro sem máscara inesperado');
-
- // Interpreta o tamanho
- $i = 2;
- $this->length = 0;
- if ($iniLength < 126)
- $this->length = $iniLength;
- else
- for ($max = $iniLength==126 ? 4 : 10; $i<$max; $i++) {
- if ($i >= $strlen-$usado)
- throw new LengthException('Fim inesperado');
- $b = ord($str[$i]);
- $this->length *= 0x100;
- $this->length += $b;
- }
- $usado = $i;
-
- // Valida o tamanho
- if ($this->opcode > 7 && $this->length > 125)
- throw new Exception('Quadro de controle muito grande');
-
- // Interpreta a máscara
- if ($this->mask)
- if ($strlen-$usado < 4)
- throw new LengthException('Fim inesperado');
- else {
- $this->maskKey = substr($str, $usado, 4);
- $usado += 4;
- }
-
- // O restante são os dados
- if ($strlen-$usado < $this->length)
- throw new LengthException('Fim inesperado');
-
- // Decodifica os dados
- $this->data = substr($str, $usado, $this->length);
- $usado += $this->length;
- if ($this->mask)
- for ($i=0; $i<$this->length; $i++)
- $this->data[$i] = $this->data[$i]^$this->maskKey[$i%4];
-
- // Interpreta o código de fechamento
- if ($this->opcode == Quadro::CLOSE && $this->length) {
- if ($this->length < 2)
- throw new Exception('Código de fechamento incompleto');
- $this->code = ord($this->data[0])*0x100+ord($this->data[1]);
- $this->data = substr($this->data, 2);
- $this->length -= 2;
- }
-
- $str = $usado==$strlen ? '' : substr($str, $usado);
- }
-
- // Junta a continuação desse fragmento nesse
- public function juntar(Quadro $outro) {
- // Verifica se a operação é válida
- if ($this->fin || $outro->opcode != Quadro::CONTINUATION)
- throw new Exception('Operação inválida');
-
- $this->fin = $outro->fin;
- $this->data .= $outro->data;
- $this->length = strlen($this->data);
- $this->buffer = '';
- }
-
- // Separa esse fragmento no número de fragmentos desejado
- // Os bits relacionados a extensões serão replicados para os fragmentos
- // Retorna uma array com esses fragmentos
- public function separar($num=2) {
- $num = (int)$num;
-
- // Verifica se a operação é válida
- if (!$this->fin || $this->opcode > 7 || $num<1)
- throw new Exception('Operação inválida');
-
- $len = ceil($this->length/$num);
- $frames = array();
- for ($i=0; $i<$num; $i++) {
- $frame = new Quadro;
- $frame->fin = $i==$num-1 ? true : false;
- $frame->rsv1 = $this->rsv1;
- $frame->rsv2 = $this->rsv2;
- $frame->rsv3 = $this->rsv3;
- $frame->opcode = $i==0 ? $this->opcode : Quadro::CONTINUATION;
- $frame->mask = $this->mask;
- $frame->maskKey = $this->maskKey;
- $frame->data = $i*$len < $this->length ? substr($this->data, $i*$len, $len) : '';
- $frame->length = strlen($frame->data);
- $frames[] = $frame;
- }
-
- return $frames;
- }
-
- // Retorna a representação do frame em string para ser enviado
- public function getString() {
- // Tenta pegar do buffer
- if ($this->buffer)
- return $this->buffer;
-
- // Primeiro byte
- $b1 = $this->fin*0x80+$this->rsv1*0x40+$this->rsv2*0x20+$this->rsv3*0x10+$this->opcode;
-
- // Coloca o código de fechamento
- $data = $this->data;
- if ($this->opcode == Quadro::CLOSE)
- if ($this->code == 1005)
- $data = '';
- else
- $data = chr($this->code>>8) . chr($this->code%0x100) . $data;
-
- // Segundo byte e tamanho
- $b2 = $this->mask*0x80;
- $str = '';
- $length = strlen($data);
- if ($length < 126)
- $b2 += $length;
- else if ($length < 0x10000) {
- $b2 += 126;
- $str = chr($length>>8) . chr($length%0x100);
- } else {
- $b2 += 127;
- $str = '';
- for ($i=0; $i<8; $i++) {
- $str .= chr($length%0x100);
- $length = floor($length/0x100);
- }
- $str = strrev($str);
- }
- $str = chr($b1) . chr($b2) . $str;
-
- // Máscara e dados
- if ($this->mask) {
- $str .= $this->maskKey;
- for ($i=0, $strlen = strlen($data); $i<$strlen; $i++)
- $data[$i] = $data[$i]^$this->maskKey[$i%4];
- }
- $str .= $data;
-
- $this->buffer = $str;
- return $str;
- }
-
- // Envia o quadro (se estiver associado a uma conexão válida)
- public function enviar() {
- if ($this->conexao && $this->conexao->getEstado() == Conexao::ABERTO)
- $this->conexao->enviarQuadro($this);
- else
- throw new Exception('Conexão inválida');
- }
-
- // Setters
- public function setConexao(Conexao $novo) {$this->conexao = $novo;}
- public function setFin($novo) {
- $novo = (boolean)$novo;
- if (!$novo && $this->opcode > 7)
- throw new Exception('Não é permitido fragmentar quadros de controle');
- $this->fin = $novo;
- $this->buffer = '';
- }
- public function setRsv1($novo) {$this->rsv1 = (boolean)$novo;$this->buffer = '';}
- public function setRsv2($novo) {$this->rsv2 = (boolean)$novo;$this->buffer = '';}
- public function setRsv3($novo) {$this->rsv3 = (boolean)$novo;$this->buffer = '';}
- public function setOpcode($novo) {
- $novo = (int)$novo;
- if ($novo<0x0 || $novo>0xF)
- throw new Exception('O valor de opcode deve ter 4 bits (0x0-0xF)');
- if (!$this->fin && $novo > 7)
- throw new Exception('Não é permitido fragmentar quadros de controle');
- $this->opcode = $novo;
- $this->buffer = '';
- }
- public function setMask($novo) {$this->mask = (boolean)$novo;$this->buffer = '';}
- // Se $novo for NULL pega um valor aleatório
- public function setMaskKey($novo=NULL) {
- if ($novo === NULL) {
- $novo = '';
- for ($i=0; $i<4; $i++)
- $novo .= chr(mt_rand(0, 255));
- } else {
- $novo = (string)$novo;
- if (strlen($novo) != 4)
- throw new Exception('O valor de maskKey deve ter 4 bytes');
- }
- $this->maskKey = $novo;
- $this->buffer = '';
- }
- public function setData($novo) {
- $novo = (string)$novo;
- if ($this->opcode > 7 && strlen($novo) > 123)
- throw new Exception('Dado muito grande para um quadro de controle');
- $this->data = $novo;
- $this->length = strlen($novo);
- $this->buffer = '';
- }
- public function setCode($novo) {
- $novo = (int)$novo;
- if ($novo<0x0 || $novo>0xFFFF)
- throw new Exception('O valor de code deve ter 2 bytes');
- else if ($novo == 1006 || $novo == 1015)
- throw new Exception('Código inválido');
- $this->code = $novo;
- $this->buffer = '';
- }
-
- // Getters
- public function getConexao() {return $this->conexao;}
- public function getFin() {return $this->fin;}
- public function getRsv1() {return $this->rsv1;}
- public function getRsv2() {return $this->rsv2;}
- public function getRsv3() {return $this->rsv3;}
- public function getOpcode() {return $this->opcode;}
- public function getMask() {return $this->mask;}
- public function getLength() {return $this->length;}
- public function getMaskKey() {return $this->maskKey;}
- public function getData() {return $this->data;}
- public function getCode() {return $this->code;}
- }