/** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { Logger::set(new NullLogger()); $this->input = $input; $this->output = $output; $this->config = $this->getContainer()->getParameter('voryx_thruway'); switch ($input->getArgument('action')) { case "start": $this->start(); break; case "stop": $this->stop(); break; case "restart": $this->restart(); break; case "status": $this->status(); break; case "add": $this->add(); break; default: $output->writeln("Expected an action: start, stop, status"); } }
public function connect(\Closure $callback, array $options = []) { $options = array_merge(['logging' => $this->logging, 'loggingOutput' => $this->loggingOutput, 'connectionTimeout' => null, 'connectionOptions' => []], $options); if (!$options['logging']) { Logger::set(new NullLogger()); } if (!$options['loggingOutput']) { ob_start(); } $connection = $this->createConnection($options['connectionOptions']); $connection->once('open', function (ClientSession $session) use($connection, $callback) { call_user_func_array($callback, [$connection, $session]); }); if ($options['connectionTimeout'] !== null) { $loop = $connection->getClient()->getLoop(); $timer = $loop->addTimer($options['connectionTimeout'], function () use($loop) { $loop->stop(); }); $connection->once('close', function () use($timer) { $timer->cancel(); \Yii::warning('WAMP connection closed by timeout.'); }); } $connection->open(); if ($options['logging']) { \Yii::info(ob_get_contents()); } if (!$options['loggingOutput']) { ob_clean(); } }
/** * Start the transport * * @param boolean $startLoop * @throws \Exception */ public function start($debug = false, $startLoop = true) { if (!$debug) { Logger::set(new NullLogger()); } parent::start($startLoop); }
/** * //@todo implement a non-blocking version of this * * @param $topicName * @param $arguments * @param array|null $argumentsKw * @param null $options * @return \React\Promise\Promise */ public function publish($topicName, $arguments, $argumentsKw = [], $options = null) { //Use the serializer to serialize and than deserialize. This is a hack because the serializer doesn't support the array format and we need to be able to handle Entities $arguments = json_decode($this->serializer->serialize($arguments, "json")); $argumentsKw = json_decode($this->serializer->serialize($argumentsKw, "json")); //If we already have a client open that we can use, use that if ($this->container->initialized('wamp_kernel') && ($client = $this->container->get('wamp_kernel')->getClient())) { $session = $this->container->get('wamp_kernel')->getSession(); return $session->publish($topicName, $arguments, $argumentsKw, $options); } if (is_array($options)) { $options = (object) $options; } if (!is_object($options)) { $options = (object) []; } Logger::set(new NullLogger()); //If we don't already have a long running client, get a short lived one. $client = $this->getShortClient(); $options->acknowledge = true; $deferrer = new Deferred(); $client->on("open", function (ClientSession $session, TransportInterface $transport) use($deferrer, $topicName, $arguments, $argumentsKw, $options) { $session->publish($topicName, $arguments, $argumentsKw, $options)->then(function () use($deferrer, $transport) { $transport->close(); $deferrer->resolve(); }); }); $client->on("error", function ($error) use($topicName) { $this->container->get('logger')->addError("Got the following error when trying to publish to '{$topicName}': {$error}"); }); $client->start(); return $deferrer->promise(); }
/** * @param SessionEvent $event */ public function onOpen(SessionEvent $event) { /* @var $mapping \Voryx\ThruwayBundle\Mapping\URIClassMapping */ foreach ($event->getResourceMappings() as $name => $mapping) { $annotation = $mapping->getAnnotation(); if (!$annotation instanceof Register) { continue; } $topicStateHandler = $annotation->getTopicStateHandlerFor(); if (!$topicStateHandler) { continue; } $session = $event->getSession(); $registration = new \stdClass(); $registration->handler_uri = $annotation->getName(); $registration->uri = $annotation->getTopicStateHandlerFor(); $registration->options = $annotation->getTopicStateHandlerOptions(); //Register Topic Handlers $registration->topic = $topicStateHandler; $session->call('add_state_handler', [$registration])->then(function ($res) use($annotation) { Logger::info($this, "Registered topic handler RPC: '{$annotation->getName()}'' for topic: '{$annotation->getTopicStateHandlerFor()}'"); }, function (ErrorMessage $error) use($annotation) { Logger::error($this, "Unable to register topic handler RPC: '{$annotation->getName()}'' for topic: '{$annotation->getTopicStateHandlerFor()}'' Error: '{$error->getErrorURI()}''"); }); } }
/** * Start transport provider * * @param \Thruway\Peer\ClientInterface $client * @param \React\EventLoop\LoopInterface $loop */ public function startTransportProvider(ClientInterface $client, LoopInterface $loop) { Logger::info($this, "Starting Transport"); $this->client = $client; $this->loop = $loop; $this->connector = new Factory($this->loop); $this->connector->__invoke($this->URL, ['wamp.2.json'])->then(function (WebSocket $conn) { Logger::info($this, "Pawl has connected"); $transport = new PawlTransport($conn, $this->loop); $transport->setSerializer(new JsonSerializer()); $this->client->onOpen($transport); $conn->on('message', function ($msg) use($transport) { Logger::debug($this, "Received: {$msg}"); try { $this->client->onMessage($transport, $transport->getSerializer()->deserialize($msg)); } catch (DeserializationException $e) { Logger::warning($this, "Deserialization exception occurred."); } catch (\Exception $e) { Logger::warning($this, "Exception occurred during onMessage: " . $e->getMessage()); } }); $conn->on('close', function ($conn) { Logger::info($this, "Pawl has closed"); $this->client->onClose('close'); }); $conn->on('pong', function ($frame, $ws) use($transport) { $transport->onPong($frame, $ws); }); }, function ($e) { $this->client->onClose('unreachable'); Logger::info($this, "Could not connect: {$e->getMessage()}"); // $this->loop->stop(); }); }
/** * TODO doc block */ public function __construct() { Logger::set(new NullLogger()); parent::__construct('realm1'); $ip = Configure::read('Websockets.ip'); $port = Configure::read('Websockets.port'); $this->addTransportProvider(new PawlTransportProvider("ws://" . $ip . ":" . $port . "/")); }
public function handleRouterStart(RouterStartEvent $event) { $socket = new Server($this->loop); $socket->on('connection', [$this, "handleConnection"]); Logger::info($this, "Raw socket listening on " . $this->address . ":" . $this->port); $socket->listen($this->port, $this->address); $this->server = $socket; }
/** * Handles session start * * @param \Thruway\AbstractSession $session * @param \Thruway\Transport\TransportProviderInterface $transport */ public function onSessionStart($session, $transport) { $this->getCallee()->register($session, "thruway.auth.{$this->getMethodName()}.onhello", [$this, 'processHello'], ["replace_orphaned_session" => "yes"])->then(function () use($session) { $this->getCallee()->register($session, "thruway.auth.{$this->getMethodName()}.onauthenticate", [$this, 'preProcessAuthenticate'], ["replace_orphaned_session" => "yes"])->then(function () use($session) { $this->getCaller()->call($session, 'thruway.auth.registermethod', [$this->getMethodName(), ["onhello" => "thruway.auth.{$this->getMethodName()}.onhello", "onauthenticate" => "thruway.auth.{$this->getMethodName()}.onauthenticate"], $this->getAuthRealms()])->then(function ($args) { Logger::debug($this, "Authentication Method Registration Successful: {$this->getMethodName()}"); }); }); }); }
public function handleRouterStart(RouterStartEvent $event) { $server = new Server($this->bindAddress, $this->port, false, ["wamp.2.json"]); Logger::info($this, "Websocket listening on " . $this->bindAddress . ":" . $this->port); $this->serverDisposable = $server->subscribe(new CallbackObserver(function (MessageSubject $ms) { $this->createNewSessionForMessageSubject($ms); }, function (\Exception $err) { Logger::error($this, "Received error on server: " . $err->getMessage()); }, function () { Logger::alert($this, "Completed. Not sure if we should ever do that."); })); }
/** * Process error * * @param \Thruway\AbstractSession $session * @param \Thruway\Message\ErrorMessage $msg */ protected function processError(AbstractSession $session, ErrorMessage $msg) { switch ($msg->getErrorMsgCode()) { case Message::MSG_SUBSCRIBE: $this->processSubscribeError($session, $msg); break; case Message::MSG_UNSUBSCRIBE: // TODO break; default: Logger::critical($this, "Unhandled error"); } }
/** * Handles session start * * @param \Thruway\ClientSession $session * @param \Thruway\Transport\TransportProviderInterface $transport */ public function onSessionStart($session, $transport) { $session->register("thruway.auth.{$this->getMethodName()}.onhello", [$this, 'processHello'], ["replace_orphaned_session" => "yes"])->then(function () use($session) { $session->register("thruway.auth.{$this->getMethodName()}.onauthenticate", [$this, 'preProcessAuthenticate'], ["replace_orphaned_session" => "yes"])->then(function () use($session) { $registrations = new \stdClass(); $registrations->onhello = "thruway.auth.{$this->getMethodName()}.onhello"; $registrations->onauthenticate = "thruway.auth.{$this->getMethodName()}.onauthenticate"; $session->call('thruway.auth.registermethod', [$this->getMethodName(), $registrations, $this->getAuthRealms()])->then(function ($args) { Logger::debug($this, "Authentication Method Registration Successful: {$this->getMethodName()}"); }); }); }); }
/** * Gets and published the topics state to this subscription * * @param Subscription $subscription * @return mixed */ public function publishState(Subscription $subscription) { //Pause all non-state building event messages $subscription->pauseForState(); $sessionId = $subscription->getSession()->getSessionId(); $this->clientSession->call($this->getProcedureName(), [$subscription->getUri(), $sessionId, $subscription->getOptions(), $subscription->getSession()->getAuthenticationDetails()])->then(function ($res) use($subscription) { $pubId = null; if (isset($res[0])) { $pubId = $res[0]; } $subscription->unPauseForState($pubId); }, function ($error) use($subscription) { Logger::error($this, "Could not call '{$this->getProcedureName()}' when restoring state"); $subscription->unPauseForState(); }); }
/** * Configure optional settings * * @param $config * @param ContainerBuilder $container */ protected function configureOptions(&$config, ContainerBuilder $container) { if ($config['enable_logging'] !== true) { Logger::set(new NullLogger()); } if (isset($config['router']['authentication']) && $config['router']['authentication'] !== false) { //Inject the authentication manager into the router $container->getDefinition('voryx.thruway.server')->addMethodCall('registerModule', [new Reference('voryx.thruway.authentication.manager')]); } if ($container->hasDefinition('security.user.provider.concrete.in_memory')) { $container->addAliases(['in_memory_user_provider' => 'security.user.provider.concrete.in_memory']); } //Topic State Handler if (isset($config['router']['enable_topic_state']) && $config['router']['enable_topic_state'] === true) { $container->getDefinition('voryx.thruway.server')->addMethodCall('registerModule', [new Reference('voryx.thruway.topic.state.handler')]); } }
/** * This should take a authid string as the argument and return * an associative array with authid, key, and salt. * * If salt is non-null, the key is the salted version of the password. * * @param $authid * @throws \Exception * @return array */ public function get($authid) { try { $userProvider = $this->container->getParameter('voryx_thruway')['user_provider']; if (null === $userProvider) { throw new \Exception('voryx_thruway.user_provider must be set.'); } $user = $this->container->get($userProvider)->loadUserByUsername($authid); if (!$user) { throw new \Exception("Can't log in, bad credentials"); } return ["user" => $user->getUsername(), "key" => $user->getPassword(), "salt" => $user->getSalt()]; } catch (\Exception $e) { Logger::error($this, $e->getMessage()); return false; } }
/** * Get Authenticate message from challenge message * * @param \Thruway\Message\ChallengeMessage $msg * @return \Thruway\Message\AuthenticateMessage|boolean */ public function getAuthenticateFromChallenge(ChallengeMessage $msg) { Logger::info($this, "Got challenge"); Logger::debug($this, "Challenge Message: " . json_encode($msg)); if (!in_array($msg->getAuthMethod(), $this->getAuthMethods())) { //throw new \Exception("method isn't in methods"); return false; } if (!is_array($msg->getDetails())) { Logger::info($this, "No details sent with challenge"); return false; } $challenge = ''; if (isset($msg->getDetails()['challenge'])) { $challenge = $msg->getDetails()['challenge']; } else { Logger::info($this, "No challenge for wampcra?"); return false; } $keyToUse = $this->key; if (isset($msg->getDetails()['salt'])) { // we need a salted key $salt = $msg->getDetails()['salt']; $keyLen = 32; if (isset($msg->getDetails()['keylen'])) { if (is_numeric($msg->getDetails()['keylen'])) { $keyLen = $msg->getDetails()['keylen']; } else { Logger::error($this, "keylen is not numeric."); } } $iterations = 1000; if (isset($msg->getDetails()['iterations'])) { if (is_numeric($msg->getDetails()['iterations'])) { $iterations = $msg->getDetails()['iterations']; } else { Logger::error($this, "iterations is not numeric."); } } $keyToUse = $this->getDerivedKey($this->key, $salt, $iterations, $keyLen); } $token = base64_encode(hash_hmac('sha256', $challenge, $keyToUse, true)); $authMessage = new AuthenticateMessage($token); Logger::debug($this, "returning: " . json_encode($authMessage)); return $authMessage; }
/** * Handle process reveived data * * @param mixed $data * @return void */ public function handleData($data) { // if ($this->handshakeByte == 0) { // $this->handshakeByte = $data[0]; // $data = substr($data, 1); // } $this->buffer = $this->buffer . $data; $bufferLen = strlen($this->buffer); while ($bufferLen > 0 && $bufferLen >= $this->msgLen) { if ($this->msgLen == 0) { // the next 4 bytes are going to be the msglen if ($bufferLen >= 4) { $this->msgLen = array_values(unpack("N", $this->buffer))[0]; if ($this->msgLen <= 0) { Logger::error("Invalid message size sent"); $this->close(); } // shift off the first 4 bytes $bufferLen = $bufferLen - 4; $this->buffer = substr($this->buffer, 4, $bufferLen); } else { // we don't have enough to get the message length return; } } if ($bufferLen >= $this->msgLen) { $msg = $this->getSerializer()->deserialize(substr($this->buffer, 0, $this->msgLen)); //$this->peer->onMessage($this, $msg); $this->emit("message", [$this, $msg]); if ($bufferLen == $this->msgLen) { $this->buffer = ""; $this->msgLen = 0; $bufferLen = 0; } else { $bufferLen = $bufferLen - $this->msgLen; $this->buffer = substr($this->buffer, $this->msgLen, $bufferLen); $this->msgLen = 0; } } } }
/** * Configure optional settings * * @param $config * @param ContainerBuilder $container */ protected function configureOptions(&$config, ContainerBuilder $container) { //Add optional Manager if (isset($config['router']['enable_manager']) && $config['router']['enable_manager'] === true) { //Replace the dummy manager with the client manager $container->getDefinition('voryx.thruway.manager.client')->setClass('Thruway\\Manager\\ManagerClient'); //Inject the manager client into the router $container->getDefinition('voryx.thruway.server')->addMethodCall('addTransportProvider', [new Reference('voryx.thruway.internal.manager')]); } if ($config['enable_logging'] !== true) { Logger::set(new NullLogger()); } if (isset($config['router']['authentication']) && $config['router']['authentication'] !== false) { //Inject the authentication manager into the router $container->getDefinition('voryx.thruway.server')->addMethodCall('registerModule', [new Reference('voryx.thruway.authentication.manager')]); } if ($container->hasDefinition('security.user.provider.concrete.in_memory')) { $container->addAliases(['in_memory_user_provider' => 'security.user.provider.concrete.in_memory']); } //Topic State Handler if (isset($config['router']['enable_topic_state']) && $config['router']['enable_topic_state'] === true) { $container->getDefinition('voryx.thruway.server')->addMethodCall('registerModule', [new Reference('voryx.thruway.topic.state.handler')]); } }
/** * @param Session $session * @param ErrorMessage $msg */ private function processInterruptError(Session $session, ErrorMessage $msg) { $call = isset($this->callInterruptIndex[$msg->getRequestId()]) ? $this->callInterruptIndex[$msg->getRequestId()] : null; if (!$call) { Logger::warning($this, "Interrupt error with no corresponding interrupt index"); return; } $errorMsgToCaller = ErrorMessage::createErrorMessageFromMessage($call->getCancelMessage()); $errorMsgToCaller->setErrorURI($msg->getErrorURI()); $callerSession = $call->getCallerSession(); $callerSession->sendMessage($errorMsgToCaller); $call->getRegistration()->removeCall($call); $this->removeCall($call); }
/** * Add new realm * * @param \Thruway\Realm $realm * @throws \Thruway\Exception\InvalidRealmNameException * @throws \Exception */ public function addRealm(Realm $realm) { $realmName = $realm->getRealmName(); if (!static::validRealmName($realm->getRealmName())) { throw new InvalidRealmNameException(); } if (array_key_exists($realm->getRealmName(), $this->realms)) { throw new \Exception("There is already a realm \"" . $realm->getRealmName() . "\""); } Logger::debug($this, "Adding realm \"" . $realmName . "\""); $this->realms[$realm->getRealmName()] = $realm; $this->router->getEventDispatcher()->dispatch('new_realm', new NewRealmEvent($realm)); }
/** * This creates a specific error message depending on the message we are reporting * an error on. * * @param \Thruway\Message\Message $msg * @param string $errorUri * @return \Thruway\Message\ErrorMessage */ public static function createErrorMessageFromMessage(Message $msg, $errorUri = null) { if ($errorUri === null) { $errorUri = "wamp.error.unknown"; } if (method_exists($msg, "getRequestId")) { return new ErrorMessage($msg->getMsgCode(), $msg->getRequestId(), new \stdClass(), $errorUri); } Logger::error(null, "Can't send an error message because the message didn't not have a request id "); }
/** * Handle close transport * * @param \Thruway\Transport\TransportInterface $transport */ public function onClose(TransportInterface $transport) { Logger::debug($this, "onClose from " . json_encode($transport->getTransportDetails())); $this->sessions->detach($transport); }
/** * Process on session leave * * @param \Thruway\Session $session */ public function leave(Session $session) { Logger::debug($this, "Leaving realm {$session->getRealm()->getRealmName()}"); if ($this->getAuthenticationManager() !== null) { $this->getAuthenticationManager()->onSessionClose($session); } foreach ($this->roles as $role) { $role->leave($session); } $this->sessions->detach($session); }
/** * process unregister * * @param \Thruway\ClientSession $session * @param string $Uri * @throws \Exception * @return \React\Promise\Promise|false */ public function unregister(ClientSession $session, $Uri) { // TODO: maybe add an option to wait for pending calls to finish $registration = null; foreach ($this->registrations as $k => $r) { if (isset($r['procedure_name'])) { if ($r['procedure_name'] == $Uri) { $registration =& $this->registrations[$k]; break; } } } if ($registration === null) { Logger::warning($this, "registration not found: " . $Uri); return false; } // we remove the callback from the client here // because we don't want the client to respond to any more calls $registration['callback'] = null; $futureResult = new Deferred(); if (!isset($registration["registration_id"])) { // this would happen if the registration was never acknowledged by the router // we should remove the registration and resolve any pending deferreds Logger::error($this, "Registration ID is not set while attempting to unregister " . $Uri); // reject the pending registration $registration['futureResult']->reject(); // TODO: need to figure out what to do in this off chance // We should still probably return a promise here that just rejects // there is an issue with the pending registration too that // the router may have a "REGISTERED" in transit and may still think that is // good to go - so maybe still send the unregister? } $requestId = Session::getUniqueId(); // save the request id so we can find this in the registration // list to call the deferred and remove it from the list $registration['unregister_request_id'] = $requestId; $registration['unregister_deferred'] = $futureResult; $unregisterMsg = new UnregisterMessage($requestId, $registration['registration_id']); $session->sendMessage($unregisterMsg); return $futureResult->promise(); }
/** * @return int */ public function decPendingCallCount() { // if we are already at zero - something is wrong if ($this->pendingCallCount == 0) { Logger::alert($this, 'Session pending call count wants to go negative.'); return 0; } return $this->pendingCallCount--; }
public function setup() { \Thruway\Logging\Logger::set(new \Psr\Log\NullLogger()); $this->router = new \Thruway\Peer\Router(); }
/** * Process Welcome message * * @param \Thruway\ClientSession $session * @param \Thruway\Message\WelcomeMessage $msg */ public function processWelcome(ClientSession $session, WelcomeMessage $msg) { Logger::info($this, "We have been welcomed..."); //TODO: I'm sure that there are some other things that we need to do here $session->setSessionId($msg->getSessionId()); $this->emit('open', [$session, $this->transport, $msg->getDetails()]); $session->setState(Session::STATE_UP); }
/** * Set manager * * @param \Thruway\Manager\ManagerInterface $manager */ public function setManager(ManagerInterface $manager) { $this->manager = $manager; Logger::info($this, "Manager attached to PawlTransportProvider"); }
/** * @param Subscription $subscription * @return StateHandlerRegistration|bool|null */ private function getStateHandlerRegistrationForSubscription(Subscription $subscription) { $subscriptionGroup = $subscription->getSubscriptionGroup(); if ($subscriptionGroup instanceof SubscriptionGroup) { if (!$this->stateHandlerMap->contains($subscriptionGroup)) { $this->setupStateHandlerRegistration($subscriptionGroup); } return $this->stateHandlerMap[$subscriptionGroup]; } Logger::alert($this, "processSubscriptionAdded called with subscription that does not have subscriptionGroup set."); return false; }
/** * Process on session leave * * @param \Thruway\Session $session */ public function leave(Session $session) { Logger::debug($this, "Leaving realm {$session->getRealm()->getRealmName()}"); $this->sessions->detach($session); }