/** * Called when new data received * @return void */ public function onRead() { $packet = $this->read(1024); $orig = $packet; $type = Binary::getByte($packet); $code = Binary::getByte($packet); $checksum = Binary::getStrWord($packet); $id = Binary::getWord($packet); $seq = Binary::getWord($packet); if ($checksum !== self::checksum(substr_replace($orig, "", 2, 2))) { $status = 'badChecksum'; } elseif ($type === 0x3) { $status = isset(static::$unreachableCodes[$code]) ? static::$unreachableCodes[$code] : 'unk' . $code . 'unreachable'; } else { $status = 'unknownType0x' . dechex($type); } while (!$this->onResponse->isEmpty()) { $el = $this->onResponse->shift(); if ($el instanceof CallbackWrapper) { $el = $el->unwrap(); } list($cb, $st) = $el; call_user_func($cb, microtime(true) - $st, $status); } $this->finish(); }
/** * 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() { start: if ($this->state === static::STATE_HEADER) { $l = $this->getInputLength(); if ($l < 2) { return; } $hdr = $this->look(2); $fb = Binary::getbitmap(ord($hdr)); $fin = (bool) $fb[0]; $opCode = bindec(substr($fb, 4, 4)); if (isset($this->opCodes[$opCode])) { $this->type = $this->opCodes[$opCode]; } else { $this->log('opCode: ' . $opCode . ': unknown frame type'); $this->finish(); return; } $sb = ord(binarySubstr($hdr, 1)); $sbm = Binary::getbitmap($sb); $this->isMasked = (bool) $sbm[0]; $payloadLength = $sb & 127; if ($payloadLength <= 125) { $this->drain(2); $this->pctLength = $payloadLength; } elseif ($payloadLength === 126) { if ($l < 4) { return; } $this->drain(2); $this->pctLength = Binary::b2i($this->read(2)); } elseif ($payloadLength === 127) { if ($l < 10) { return; } $this->drain(2); $this->pctLength = Binary::b2i($this->read(8)); } if ($this->pool->maxAllowedPacket < $this->pctLength) { Daemon::$process->log('max-allowed-packet (' . $this->pool->config->maxallowedpacket->getHumanValue() . ') exceed, aborting connection'); $this->finish(); return; } $this->setWatermark($this->pctLength + ($this->isMasked ? 4 : 0)); $this->state = static::STATE_DATA; } if ($this->state === static::STATE_DATA) { if ($this->getInputLength() < $this->pctLength + ($this->isMasked ? 4 : 0)) { return; } $this->state = static::STATE_HEADER; $this->setWatermark(2); if ($this->isMasked) { $this->trigger('frame', static::mask($this->read(4), $this->read($this->pctLength))); } else { $this->trigger('frame', $this->read($this->pctLength)); } } if ($this->state == static::STATE_STANDBY) { while (($line = $this->readLine()) !== null) { $line = trim($line); if ($line == '') { $expectedKey = base64_encode(pack('H*', sha1($this->key . static::GUID))); if (isset($this->headers['HTTP_SEC_WEBSOCKET_ACCEPT']) && $expectedKey == $this->headers['HTTP_SEC_WEBSOCKET_ACCEPT']) { $this->state = static::STATE_HEADER; if ($this->onConnected) { $this->connected = true; $this->onConnected->executeAll($this); $this->onConnected = null; } $this->trigger('connected'); goto start; } else { Daemon::$process->log(__METHOD__ . ': Handshake failed. Connection to ' . $this->url . ' failed.'); $this->finish(); } } else { $e = explode(': ', $line); if (isset($e[1])) { $this->headers['HTTP_' . strtoupper(strtr($e[0], ['-' => '_']))] = $e[1]; } } } return; } goto start; }
/** * Gets the host information * @param string $hostname Hostname * @param callable $cb Callback * @callback $cb ( ) * @return void */ public function get($hostname, $cb) { $this->onResponse->push($cb); $this->setFree(false); $e = explode(':', $hostname, 3); $hostname = $e[0]; $qtype = isset($e[1]) ? $e[1] : 'A'; $qclass = isset($e[2]) ? $e[2] : 'IN'; $QD = []; $qtypeInt = array_search($qtype, Pool::$type, true); $qclassInt = array_search($qclass, Pool::$class, true); if ($qtypeInt === false || $qclassInt === false) { call_user_func($cb, false); return; } $q = Binary::labels($hostname) . Binary::word($qtypeInt) . Binary::word($qclassInt); $QD[] = $q; $packet = Binary::word(++$this->seq) . Binary::bitmap2bytes('0' . '0000' . '0' . '0' . '1' . '0' . '000' . '0000', 2) . Binary::word(sizeof($QD)) . Binary::word(0) . Binary::word(0) . Binary::word(0) . implode('', $QD); if ($this->type === 'udp') { $this->write($packet); } else { $this->write(Binary::word(strlen($packet)) . $packet); } }
/** * onRead * @return void */ protected function onRead() { start: if ($this->state === static::STATE_STANDBY) { if (($hdr = $this->readExact(2)) === false) { return; // not enough data } $u = unpack('S', $hdr); $this->responseCode = $u[1]; $this->state = static::STATE_PACKET_HDR; } if ($this->state === static::STATE_PACKET_HDR) { if ($this->responseCode === static::REPL_KVAL) { $this->result = []; if (($hdr = $this->readExact(9)) === false) { return; // not enough data } $this->encoding = Binary::getByte($hdr); $this->responseLength = Binary::getDword($hdr, true) - 4; $this->totalNum = Binary::getDword($hdr, true); $this->readedNum = 0; $this->state = static::STATE_PACKET_DATA; } else { if (($hdr = $this->lookExact(5)) === false) { return; // not enough data } $this->encoding = Binary::getByte($hdr); $pl = Binary::getDword($hdr, true); if ($this->getInputLength() < 5 + $pl) { return; // not enough data } $this->drain(5); $this->responseLength = $pl; if ($this->responseLength > $this->pool->maxAllowedPacket) { $this->log('max-allowed-packet (' . $this->pool->config->maxallowedpacket->getHumanValue() . ') exceed, aborting connection'); $this->finish(); return; } if ($this->responseCode === static::REPL_ERR_NOT_FOUND) { $this->drain($this->responseLength); $this->result = null; $this->isFinal = true; $this->totalNum = 0; $this->readedNum = 0; $this->executeCb(); } elseif ($this->responseCode === static::REPL_OK) { $this->drain($this->responseLength); $this->result = true; $this->isFinal = true; $this->totalNum = 0; $this->readedNum = 0; $this->executeCb(); } elseif ($this->responseCode === static::REPL_ERR_MEM || $this->responseCode === static::REPL_ERR_NAN || $this->responseCode === static::REPL_ERR_LOCKED) { $this->drain($this->responseLength); $this->result = false; $this->isFinal = true; $this->totalNum = 0; $this->readedNum = 0; $this->executeCb(); } else { if ($this->responseCode === static::REPL_KVAL && $this->totalNum <= 0) { $this->drain($this->responseLength); $this->isFinal = true; $this->totalNum = 0; $this->readedNum = 0; $this->result = []; $this->executeCb(); } else { $this->state = static::STATE_PACKET_DATA; } } } } if ($this->state === static::STATE_PACKET_DATA) { if ($this->responseCode === static::REPL_KVAL) { $keyAdded = false; nextElement: $l = $this->getInputLength(); if ($l < 9) { goto cursorCall; } if (($hdr = $this->lookExact($o = 4)) === false) { goto cursorCall; } $keyLen = Binary::getDword($hdr, true); if (($key = $this->lookExact($keyLen, $o)) === false) { goto cursorCall; } $o += $keyLen; if (($encoding = $this->lookExact(1, $o)) === false) { goto cursorCall; } $encoding = ord($encoding); ++$o; if (($hdr = $this->lookExact(4, $o)) === false) { goto cursorCall; } $o += 4; $valLen = Binary::getDword($hdr, true); if ($o + $valLen > $l) { goto cursorCall; } $this->drain($o); if ($encoding === static::GB_ENC_NUMBER) { $val = $this->read($valLen); $this->result[$key] = $valLen === 8 ? Binary::getQword($val, true) : Binary::getDword($val, true); } else { $this->result[$key] = $this->read($valLen); } $keyAdded = true; if (++$this->readedNum >= $this->totalNum) { $this->isFinal = true; $this->executeCb(); goto start; } else { goto nextElement; } cursorCall: if ($keyAdded) { $this->onResponse->executeAndKeepOne($this); } return; } else { if (($this->result = $this->readExact($this->responseLength)) === false) { $this->setWatermark($this->responseLength); return; } $this->setWatermark(2, $this->pool->maxAllowedPacket); if ($this->encoding === static::GB_ENC_NUMBER) { $this->result = $this->responseLength === 8 ? Binary::getQword($this->result, true) : Binary::getDword($this->result, true); } $this->isFinal = true; $this->totalNum = 1; $this->readedNum = 1; $this->executeCb(); } } goto start; }
/** * 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; }