* @copyright 2014 Fabian Grutschus. All rights reserved. * @license BSD * @link http://github.com/fabiang/xmpp */ namespace Fabiang\Xmpp\Connection; use Psr\Log\LogLevel; use Fabiang\Xmpp\Stream\SocketClient; use Fabiang\Xmpp\Util\XML; use Fabiang\Xmpp\Options; use Fabiang\Xmpp\Exception\TimeoutException; /** * Connection to a socket stream. * * @package Xmpp\Connection */ class Socket extends AbstractConnection implements SocketConnectionInterface { const DEFAULT_LENGTH = 4096; const STREAM_START = <<<'XML' XML; const STREAM_END = ''; /** * Socket. * * @var SocketClient */ protected $socket; /** * Did we received any data yet? * * @var bool */ private $receivedAnyData = false; /** * Constructor set default socket instance if no socket was given. * * @param StreamSocket $socket Socket instance */ public function __construct(SocketClient $socket) { $this->setSocket($socket); } /** * Factory for connection class. * * @param Options $options Options object * @return static */ public static function factory(Options $options) { $socket = new SocketClient($options->getAddress(), $options->getContextOptions()); $object = new static($socket); $object->setOptions($options); return $object; } /** * {@inheritDoc} */ public function receive() { $buffer = $this->getSocket()->read(static::DEFAULT_LENGTH); if ($buffer) { $this->receivedAnyData = true; $address = $this->getAddress(); $this->log("Received buffer '$buffer' from '{$address}'", LogLevel::DEBUG); $this->getInputStream()->parse($buffer); return $buffer; } try { $this->checkTimeout($buffer); } catch (TimeoutException $exception) { $this->reconnectTls($exception); } } /** * Try to reconnect via TLS. * * @param TimeoutException $exception * @return null * @throws TimeoutException */ private function reconnectTls(TimeoutException $exception) { // check if we didn't receive any data // if not we re-try to connect via TLS if (false === $this->receivedAnyData) { $matches = []; $previousAddress = $this->getOptions()->getAddress(); // only reconnect via tls if we've used tcp before. if (preg_match('#tcp://(?
.+)#', $previousAddress, $matches)) { $this->log('Connecting via TCP failed, now trying to connect via TLS'); $address = 'tls://' . $matches['address']; $this->connected = false; $this->getOptions()->setAddress($address); $this->getSocket()->reconnect($address); $this->connect(); return; } } throw $exception; } /** * {@inheritDoc} */ public function send($buffer) { if (false === $this->isConnected()) { $this->connect(); } $address = $this->getAddress(); $this->log("Sending data '$buffer' to '{$address}'", LogLevel::DEBUG); $this->getSocket()->write($buffer); $this->getOutputStream()->parse($buffer); while ($this->checkBlockingListeners()) { $this->receive(); } } /** * {@inheritDoc} */ public function connect() { if (false === $this->connected) { $address = $this->getAddress(); $this->getSocket()->connect($this->getOptions()->getTimeout()); $this->getSocket()->setBlocking(true); $this->connected = true; $this->log("Connected to '{$address}'", LogLevel::DEBUG); } $this->send(sprintf(static::STREAM_START, XML::quote($this->getOptions()->getTo()))); } /** * {@inheritDoc} */ public function disconnect() { if (true === $this->connected) { $address = $this->getAddress(); $this->send(static::STREAM_END); $this->getSocket()->close(); $this->connected = false; $this->log("Disconnected from '{$address}'", LogLevel::DEBUG); } } /** * Get address from options object. * * @return string */ protected function getAddress() { return $this->getOptions()->getAddress(); } /** * Return socket instance. * * @return SocketClient */ public function getSocket() { return $this->socket; } /** * {@inheritDoc} */ public function setSocket(SocketClient $socket) { $this->socket = $socket; return $this; } }