/** * Called when new data received * @return void */ public function onRead() { start: if ($this->type === 'udp') { $this->onUdpPacket($this->read($this->getInputLength())); return; } if ($this->state === self::STATE_ROOT) { if (false === ($hdr = $this->readExact(2))) { return; // not enough data } $this->pctSize = Binary::bytes2int($hdr); $this->setWatermark($this->pctSize); $this->state = self::STATE_PACKET; } if ($this->state === self::STATE_PACKET) { if (false === ($pct = $this->readExact($this->pctSize))) { return; // not enough data } $this->state = self::STATE_ROOT; $this->setWatermark(2); $this->onUdpPacket($pct); } goto start; }
/** * Parse structure of labels * @param string &$data Binary data * @param string $orig Original packet * @return string Dot-separated labels list */ public static function parseLabels(&$data, $orig = null) { $str = ''; while (strlen($data) > 0) { $l = ord($data[0]); if ($l >= 192) { $pos = Binary::bytes2int(chr($l - 192) . binarySubstr($data, 1, 1)); $data = binarySubstr($data, 2); $ref = binarySubstr($orig, $pos); return $str . Binary::parseLabels($ref); } $p = substr($data, 1, $l); $str .= $p . ($l !== 0 ? '.' : ''); $data = substr($data, $l + 1); if ($l === 0) { break; } } return $str; }
/** * Called when new data received * @see http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10#page-16 * @return void */ public function onRead() { if ($this->state === self::STATE_PREHANDSHAKE) { if (!$this->handshake()) { return; } } if ($this->state === self::STATE_HANDSHAKED) { while (($buflen = $this->getInputLength()) >= 2) { $first = ord($this->look(1)); // first byte integer (fin, opcode) $firstBits = decbin($first); $opcode = (int) bindec(substr($firstBits, 4, 4)); if ($opcode === 0x8) { // CLOSE $this->finish(); return; } $opcodeName = isset(static::$opcodes[$opcode]) ? static::$opcodes[$opcode] : false; if (!$opcodeName) { Daemon::log(get_class($this) . ': Undefined opcode ' . $opcode); $this->finish(); return; } $second = ord($this->look(1, 1)); // second byte integer (masked, payload length) $fin = (bool) ($first >> 7); $isMasked = (bool) ($second >> 7); $dataLength = $second & 0x7f; $p = 2; if ($dataLength === 0x7e) { // 2 bytes-length if ($buflen < $p + 2) { return; // not enough data yet } $dataLength = Binary::bytes2int($this->look(2, $p), false); $p += 2; } elseif ($dataLength === 0x7f) { // 8 bytes-length if ($buflen < $p + 8) { return; // not enough data yet } $dataLength = Binary::bytes2int($this->look(8, $p)); $p += 8; } if ($this->pool->maxAllowedPacket <= $dataLength) { // Too big packet $this->finish(); return; } if ($isMasked) { if ($buflen < $p + 4) { return; // not enough data yet } $mask = $this->look(4, $p); $p += 4; } if ($buflen < $p + $dataLength) { return; // not enough data yet } $this->drain($p); $data = $this->read($dataLength); if ($isMasked) { $data = $this->mask($data, $mask); } //Daemon::log(Debug::dump(array('ext' => $this->extensions, 'rsv1' => $firstBits[1], 'data' => Debug::exportBytes($data)))); /*if ($firstBits[1] && in_array('deflate-frame', $this->extensions)) { // deflate frame $data = gzuncompress($data, $this->pool->maxAllowedPacket); }*/ if (!$fin) { $this->framebuf .= $data; } else { $this->onFrame($this->framebuf . $data, $opcodeName); $this->framebuf = ''; } } } }
/** * Called when new data received * @return void */ public function onRead() { packet: if ($this->state === self::STATE_STANDBY) { if ($this->bev->input->length < 4) { return; } $this->pctSize = Binary::bytes2int($this->read(3), true); $this->setWatermark($this->pctSize, $this->pctSize); $this->state = self::STATE_BODY; $this->seq = ord($this->read(1)) + 1; } /* STATE_BODY */ $l = $this->bev->input->length; if ($l < $this->pctSize) { return; } $this->state = self::STATE_STANDBY; $this->setWatermark(4); if ($this->phase === 0) { $this->phase = self::PHASE_GOT_INIT; $this->protover = ord($this->read(1)); if ($this->protover === 0xff) { // error $fieldCount = $this->protover; $this->protover = 0; $this->onResponse->push(function ($conn, $result) { if ($conn->onConnected) { $conn->connected = true; $conn->onConnected->executeAll($conn, $result); $conn->onConnected = null; } }); goto field; } if (($p = $this->search("")) === false) { $this->log('nul-terminator of \'serverver\' is not found'); $this->finish(); return; } $this->serverver = $this->read($p); $this->drain(1); // drain nul-byte $this->threadId = Binary::bytes2int($this->read(4), true); $this->scramble = $this->read(8); $this->drain(1); // ???? $this->serverCaps = Binary::bytes2int($this->read(2), true); $this->serverLang = ord($this->read(1)); $this->serverStatus = Binary::bytes2int($this->read(2), true); $this->drain(13); $restScramble = $this->read(12); $this->scramble .= $restScramble; $this->drain(1); $this->auth(); } else { $fieldCount = ord($this->read(1)); field: if ($fieldCount === 0xff) { // Error packet $u = unpack('v', $this->read(2)); $this->errno = $u[1]; $state = $this->read(6); $this->errmsg = $this->read($this->pctSize - $l + $this->bev->input->length); $this->onError(); $this->errno = 0; $this->errmsg = ''; } elseif ($fieldCount === 0x0) { // OK Packet Empty if ($this->phase === self::PHASE_AUTH_SENT) { $this->phase = self::PHASE_HANDSHAKED; if ($this->dbname !== '') { $this->query('USE `' . $this->dbname . '`'); } } $this->affectedRows = $this->parseEncodedBinary(); $this->insertId = $this->parseEncodedBinary(); $u = unpack('v', $this->read(2)); $this->serverStatus = $u[1]; $u = unpack('v', $this->read(2)); $this->warnCount = $u[1]; $this->message = $this->read($this->pctSize - $l + $this->bev->input->length); $this->onResultDone(); } elseif ($fieldCount === 0xfe) { // EOF Packet if ($this->rsState === self::RS_STATE_ROW) { $this->onResultDone(); } else { ++$this->rsState; } } else { // Data packet $this->prependInput(chr($fieldCount)); if ($this->rsState === self::RS_STATE_HEADER) { // Result Set Header Packet $extra = $this->parseEncodedBinary(); $this->rsState = self::RS_STATE_FIELD; } elseif ($this->rsState === self::RS_STATE_FIELD) { // Field Packet $field = ['catalog' => $this->parseEncodedString(), 'db' => $this->parseEncodedString(), 'table' => $this->parseEncodedString(), 'org_table' => $this->parseEncodedString(), 'name' => $this->parseEncodedString(), 'org_name' => $this->parseEncodedString()]; $this->drain(1); // filler $u = unpack('v', $this->read(2)); $field['charset'] = $u[1]; $u = unpack('V', $this->read(4)); $field['length'] = $u[1]; $field['type'] = ord($this->read(1)); $u = unpack('v', $this->read(2)); $field['flags'] = $u[1]; $field['decimals'] = ord($this->read(1)); $this->resultFields[] = $field; } elseif ($this->rsState === self::RS_STATE_ROW) { // Row Packet $row = []; for ($i = 0, $nf = sizeof($this->resultFields); $i < $nf; ++$i) { $row[$this->resultFields[$i]['name']] = $this->parseEncodedString(); } $this->resultRows[] = $row; } } } if ($this->finished) { return; } $this->drain($this->pctSize - $l + $this->bev->input->length); // drain the rest of packet goto packet; }