diff --git a/lib/PhpAmqpLib/Channel/AMQPChannel.php b/lib/PhpAmqpLib/Channel/AMQPChannel.php new file mode 100644 index 0000000000..21590c43bb --- /dev/null +++ b/lib/PhpAmqpLib/Channel/AMQPChannel.php @@ -0,0 +1,1489 @@ +get_free_channel_id(); + } + + parent::__construct($connection, $channel_id); + + $this->publish_cache = array(); + $this->publish_cache_max_size = 100; + + if ($this->debug) { + MiscHelper::debug_msg('using channel_id: ' . $channel_id); + } + + $this->default_ticket = 0; + $this->is_open = false; + $this->active = true; // Flow control + $this->alerts = array(); + $this->callbacks = array(); + $this->auto_decode = $auto_decode; + + try { + $this->x_open(); + } catch (\Exception $e) { + $this->close(); + throw $e; + } + } + + /** + * Tear down this object, after we've agreed to close with the server. + */ + protected function do_close() + { + if ($this->channel_id !== null) { + unset($this->connection->channels[$this->channel_id]); + } + $this->channel_id = $this->connection = null; + $this->is_open = false; + } + + /** + * Only for AMQP0.8.0 + * This method allows the server to send a non-fatal warning to + * the client. This is used for methods that are normally + * asynchronous and thus do not have confirmations, and for which + * the server may detect errors that need to be reported. Fatal + * errors are handled as channel or connection exceptions; non- + * fatal errors are sent through this method. + * + * @param AMQPReader $args + */ + protected function channel_alert($args) + { + $reply_code = $args->read_short(); + $reply_text = $args->read_shortstr(); + $details = $args->read_table(); + array_push($this->alerts, array($reply_code, $reply_text, $details)); + } + + /** + * Request a channel close + * + * @param int $reply_code + * @param string $reply_text + * @param array $method_sig + * @return mixed + */ + public function close($reply_code = 0, $reply_text = '', $method_sig = array(0, 0)) + { + if ($this->is_open !== true || null === $this->connection) { + $this->do_close(); + + return; // already closed + } + list($class_id, $method_id, $args) = $this->protocolWriter->channelClose( + $reply_code, + $reply_text, + $method_sig[0], + $method_sig[1] + ); + + $this->send_method_frame(array($class_id, $method_id), $args); + + return $this->wait(array( + $this->waitHelper->get_wait('channel.close_ok') + )); + } + + /** + * @param AMQPReader $args + * @throws \PhpAmqpLib\Exception\AMQPProtocolChannelException + */ + protected function channel_close($args) + { + $reply_code = $args->read_short(); + $reply_text = $args->read_shortstr(); + $class_id = $args->read_short(); + $method_id = $args->read_short(); + + $this->send_method_frame(array(20, 41)); + $this->do_close(); + + throw new AMQPProtocolChannelException($reply_code, $reply_text, array($class_id, $method_id)); + } + + /** + * Confirm a channel close + * Alias of AMQPChannel::do_close() + * + * @param AMQPReader $args + */ + protected function channel_close_ok($args) + { + $this->do_close(); + } + + /** + * Enables/disables flow from peer + * + * @param $active + * @return mixed + */ + public function flow($active) + { + list($class_id, $method_id, $args) = $this->protocolWriter->channelFlow($active); + $this->send_method_frame(array($class_id, $method_id), $args); + + return $this->wait(array( + $this->waitHelper->get_wait('channel.flow_ok') + )); + } + + /** + * @param AMQPReader $args + */ + protected function channel_flow($args) + { + $this->active = $args->read_bit(); + $this->x_flow_ok($this->active); + } + + /** + * @param $active + */ + protected function x_flow_ok($active) + { + list($class_id, $method_id, $args) = $this->protocolWriter->channelFlow($active); + $this->send_method_frame(array($class_id, $method_id), $args); + } + + /** + * @param AMQPReader $args + * @return bool + */ + protected function channel_flow_ok($args) + { + return $args->read_bit(); + } + + /** + * @param string $out_of_band + * @return mixed + */ + protected function x_open($out_of_band = '') + { + if ($this->is_open) { + return null; + } + + list($class_id, $method_id, $args) = $this->protocolWriter->channelOpen($out_of_band); + $this->send_method_frame(array($class_id, $method_id), $args); + + return $this->wait(array( + $this->waitHelper->get_wait('channel.open_ok') + )); + } + + /** + * @param AMQPReader $args + */ + protected function channel_open_ok($args) + { + $this->is_open = true; + + if ($this->debug) { + MiscHelper::debug_msg('Channel open'); + } + } + + /** + * Requests an access ticket + * + * @param string $realm + * @param bool $exclusive + * @param bool $passive + * @param bool $active + * @param bool $write + * @param bool $read + * @return mixed + */ + public function access_request( + $realm, + $exclusive = false, + $passive = false, + $active = false, + $write = false, + $read = false + ) { + list($class_id, $method_id, $args) = $this->protocolWriter->accessRequest( + $realm, + $exclusive, + $passive, + $active, + $write, + $read + ); + + $this->send_method_frame(array($class_id, $method_id), $args); + + return $this->wait(array( + $this->waitHelper->get_wait('access.request_ok') + )); + } + + /** + * Grants access to server resources + * + * @param AMQPReader $args + * @return string + */ + protected function access_request_ok($args) + { + $this->default_ticket = $args->read_short(); + + return $this->default_ticket; + } + + /** + * Declares exchange + * + * @param string $exchange + * @param string $type + * @param bool $passive + * @param bool $durable + * @param bool $auto_delete + * @param bool $internal + * @param bool $nowait + * @return mixed|null + */ + public function exchange_declare( + $exchange, + $type, + $passive = false, + $durable = false, + $auto_delete = true, + $internal = false, + $nowait = false, + $arguments = null, + $ticket = null + ) { + $arguments = $this->getArguments($arguments); + $ticket = $this->getTicket($ticket); + + list($class_id, $method_id, $args) = $this->protocolWriter->exchangeDeclare( + $ticket, + $exchange, + $type, + $passive, + $durable, + $auto_delete, + $internal, + $nowait, + $arguments + ); + + $this->send_method_frame(array($class_id, $method_id), $args); + + if ($nowait) { + return null; + } + + return $this->wait(array( + $this->waitHelper->get_wait('exchange.declare_ok') + )); + } + + /** + * Confirms an exchange declaration + */ + protected function exchange_declare_ok($args) + { + } + + /** + * Deletes an exchange + * + * @param string $exchange + * @param bool $if_unused + * @param bool $nowait + * @param null $ticket + * @return mixed|null + */ + public function exchange_delete( + $exchange, + $if_unused = false, + $nowait = false, + $ticket = null + ) { + $ticket = $this->getTicket($ticket); + list($class_id, $method_id, $args) = $this->protocolWriter->exchangeDelete( + $ticket, + $exchange, + $if_unused, + $nowait + ); + + $this->send_method_frame(array($class_id, $method_id), $args); + + if ($nowait) { + return null; + } + + return $this->wait(array( + $this->waitHelper->get_wait('exchange.delete_ok') + )); + } + + /** + * Confirms deletion of an exchange + */ + protected function exchange_delete_ok($args) + { + } + + /** + * Binds dest exchange to source exchange + * + * @param string $destination + * @param string $source + * @param string $routing_key + * @param bool $nowait + * @param null $arguments + * @param null $ticket + * @return mixed|null + */ + public function exchange_bind( + $destination, + $source, + $routing_key = '', + $nowait = false, + $arguments = null, + $ticket = null + ) { + $arguments = $this->getArguments($arguments); + $ticket = $this->getTicket($ticket); + + list($class_id, $method_id, $args) = $this->protocolWriter->exchangeBind( + $ticket, + $destination, + $source, + $routing_key, + $nowait, + $arguments + ); + + $this->send_method_frame(array($class_id, $method_id), $args); + + if ($nowait) { + return null; + } + + return $this->wait(array( + $this->waitHelper->get_wait('exchange.bind_ok') + )); + } + + /** + * Confirms bind successful + */ + protected function exchange_bind_ok($args) + { + } + + /** + * Unbinds dest exchange from source exchange + * + * @param string $destination + * @param string $source + * @param string $routing_key + * @param null $arguments + * @param null $ticket + * @return mixed + */ + public function exchange_unbind($destination, $source, $routing_key = '', $arguments = null, $ticket = null) + { + $arguments = $this->getArguments($arguments); + $ticket = $this->getTicket($ticket); + + list($class_id, $method_id, $args) = $this->protocolWriter->exchangeUnbind( + $ticket, + $destination, + $source, + $routing_key, + $arguments + ); + + $this->send_method_frame(array($class_id, $method_id), $args); + + return $this->wait(array( + $this->waitHelper->get_wait('exchange.unbind_ok') + )); + } + + /** + * Confirms unbind successful + */ + protected function exchange_unbind_ok($args) + { + } + + /** + * Binds queue to an exchange + * + * @param string $queue + * @param string $exchange + * @param string $routing_key + * @param bool $nowait + * @param null $arguments + * @param null $ticket + * @return mixed|null + */ + public function queue_bind($queue, $exchange, $routing_key = '', $nowait = false, $arguments = null, $ticket = null) + { + $arguments = $this->getArguments($arguments); + $ticket = $this->getTicket($ticket); + + list($class_id, $method_id, $args) = $this->protocolWriter->queueBind( + $ticket, + $queue, + $exchange, + $routing_key, + $nowait, + $arguments + ); + + $this->send_method_frame(array($class_id, $method_id), $args); + + if ($nowait) { + return null; + } + + return $this->wait(array( + $this->waitHelper->get_wait('queue.bind_ok') + )); + } + + /** + * Confirms bind successful + */ + protected function queue_bind_ok($args) + { + } + + /** + * Unbind queue from an exchange + * + * @param string $queue + * @param string $exchange + * @param string $routing_key + * @param null $arguments + * @param null $ticket + * @return mixed + */ + public function queue_unbind($queue, $exchange, $routing_key = '', $arguments = null, $ticket = null) + { + $arguments = $this->getArguments($arguments); + $ticket = $this->getTicket($ticket); + + list($class_id, $method_id, $args) = $this->protocolWriter->queueUnbind( + $ticket, + $queue, + $exchange, + $routing_key, + $arguments + ); + + $this->send_method_frame(array($class_id, $method_id), $args); + + return $this->wait(array( + $this->waitHelper->get_wait('queue.unbind_ok') + )); + } + + /** + * Confirms unbind successful + */ + protected function queue_unbind_ok($args) + { + } + + /** + * Declares queue, creates if needed + * + * @param string $queue + * @param bool $passive + * @param bool $durable + * @param bool $exclusive + * @param bool $auto_delete + * @param bool $nowait + * @param null $arguments + * @param null $ticket + * @return mixed|null + */ + public function queue_declare( + $queue = '', + $passive = false, + $durable = false, + $exclusive = false, + $auto_delete = true, + $nowait = false, + $arguments = null, + $ticket = null + ) { + $arguments = $this->getArguments($arguments); + $ticket = $this->getTicket($ticket); + + list($class_id, $method_id, $args) = $this->protocolWriter->queueDeclare( + $ticket, + $queue, + $passive, + $durable, + $exclusive, + $auto_delete, + $nowait, + $arguments + ); + + $this->send_method_frame(array($class_id, $method_id), $args); + + if ($nowait) { + return null; + } + + return $this->wait(array( + $this->waitHelper->get_wait('queue.declare_ok') + )); + } + + /** + * Confirms a queue definition + * + * @param AMQPReader $args + * @return array + */ + protected function queue_declare_ok($args) + { + $queue = $args->read_shortstr(); + $message_count = $args->read_long(); + $consumer_count = $args->read_long(); + + return array($queue, $message_count, $consumer_count); + } + + /** + * Deletes a queue + * + * @param string $queue + * @param bool $if_unused + * @param bool $if_empty + * @param bool $nowait + * @param null $ticket + * @return mixed|null + */ + public function queue_delete($queue = '', $if_unused = false, $if_empty = false, $nowait = false, $ticket = null) + { + $ticket = $this->getTicket($ticket); + + list($class_id, $method_id, $args) = $this->protocolWriter->queueDelete( + $ticket, + $queue, + $if_unused, + $if_empty, + $nowait + ); + + $this->send_method_frame(array($class_id, $method_id), $args); + + if ($nowait) { + return null; + } + + return $this->wait(array( + $this->waitHelper->get_wait('queue.delete_ok') + )); + } + + /** + * Confirms deletion of a queue + * + * @param AMQPReader $args + * @return string + */ + protected function queue_delete_ok($args) + { + return $args->read_long(); + } + + /** + * Purges a queue + * + * @param string $queue + * @param bool $nowait + * @param null $ticket + * @return mixed|null + */ + public function queue_purge($queue = '', $nowait = false, $ticket = null) + { + $ticket = $this->getTicket($ticket); + list($class_id, $method_id, $args) = $this->protocolWriter->queuePurge($ticket, $queue, $nowait); + + $this->send_method_frame(array($class_id, $method_id), $args); + + if ($nowait) { + return null; + } + + return $this->wait(array( + $this->waitHelper->get_wait('queue.purge_ok') + )); + } + + /** + * Confirms a queue purge + * + * @param AMQPReader $args + * @return string + */ + protected function queue_purge_ok($args) + { + return $args->read_long(); + } + + /** + * Acknowledges one or more messages + * + * @param string $delivery_tag + * @param bool $multiple + */ + public function basic_ack($delivery_tag, $multiple = false) + { + list($class_id, $method_id, $args) = $this->protocolWriter->basicAck($delivery_tag, $multiple); + $this->send_method_frame(array($class_id, $method_id), $args); + } + + /** + * Called when the server sends a basic.ack + * + * @param AMQPReader $args + * @throws AMQPRuntimeException + */ + protected function basic_ack_from_server(AMQPReader $args) + { + $delivery_tag = $args->read_longlong(); + $multiple = (bool) $args->read_bit(); + + if (false === isset($this->published_messages[$delivery_tag])) { + throw new AMQPRuntimeException(sprintf( + 'Server ack\'ed unknown delivery_tag %s', + $delivery_tag + )); + } + + $this->internal_ack_handler($delivery_tag, $multiple, $this->ack_handler); + } + + /** + * Called when the server sends a basic.nack + * + * @param AMQPReader $args + * @throws AMQPRuntimeException + */ + protected function basic_nack_from_server($args) + { + $delivery_tag = $args->read_longlong(); + $multiple = (bool) $args->read_bit(); + + if (false === isset($this->published_messages[$delivery_tag])) { + throw new AMQPRuntimeException(sprintf( + 'Server nack\'ed unknown delivery_tag %s', + $delivery_tag + )); + } + + $this->internal_ack_handler($delivery_tag, $multiple, $this->nack_handler); + } + + /** + * Handles the deletion of messages from this->publishedMessages and dispatches them to the $handler + * + * @param string $delivery_tag + * @param bool $multiple + * @param $handler + */ + protected function internal_ack_handler($delivery_tag, $multiple, $handler) + { + if ($multiple) { + $keys = $this->get_keys_less_or_equal($this->published_messages, $delivery_tag); + + foreach ($keys as $key) { + $this->internal_ack_handler($key, false, $handler); + } + + } else { + $message = $this->get_and_unset_message($delivery_tag); + $this->dispatch_to_handler($handler, array($message)); + } + } + + /** + * @param array $array + * @param $value + * @return mixed + */ + protected function get_keys_less_or_equal(array $array, $value) + { + $keys = array_reduce( + array_keys($array), + function ($keys, $key) use ($value) { + if (bccomp($key, $value, 0) <= 0) { + $keys[] = $key; + } + + return $keys; + }, + array() + ); + + return $keys; + } + + /** + * Rejects one or several received messages + * + * @param string $delivery_tag + * @param bool $multiple + * @param bool $requeue + */ + public function basic_nack($delivery_tag, $multiple = false, $requeue = false) + { + list($class_id, $method_id, $args) = $this->protocolWriter->basicNack($delivery_tag, $multiple, $requeue); + $this->send_method_frame(array($class_id, $method_id), $args); + } + + /** + * Ends a queue consumer + * + * @param string $consumer_tag + * @param bool $nowait + * @return mixed + */ + public function basic_cancel($consumer_tag, $nowait = false) + { + list($class_id, $method_id, $args) = $this->protocolWriter->basicCancel($consumer_tag, $nowait); + $this->send_method_frame(array($class_id, $method_id), $args); + + return $this->wait(array( + $this->waitHelper->get_wait('basic.cancel_ok') + )); + } + + /** + * @param AMQPReader $args + * @throws \PhpAmqpLib\Exception\AMQPBasicCancelException + */ + protected function basic_cancel_from_server(AMQPReader $args) + { + $consumerTag = $args->read_shortstr(); + + throw new AMQPBasicCancelException($consumerTag); + } + + /** + * Confirm a cancelled consumer + * + * @param AMQPReader $args + */ + protected function basic_cancel_ok($args) + { + $consumer_tag = $args->read_shortstr(); + unset($this->callbacks[$consumer_tag]); + } + + /** + * Starts a queue consumer + * + * @param string $queue + * @param string $consumer_tag + * @param bool $no_local + * @param bool $no_ack + * @param bool $exclusive + * @param bool $nowait + * @param callback|null $callback + * @param int|null $ticket + * @param array $arguments + * @return mixed|string + */ + public function basic_consume( + $queue = '', + $consumer_tag = '', + $no_local = false, + $no_ack = false, + $exclusive = false, + $nowait = false, + $callback = null, + $ticket = null, + $arguments = array() + ) { + $ticket = $this->getTicket($ticket); + list($class_id, $method_id, $args) = $this->protocolWriter->basicConsume( + $ticket, + $queue, + $consumer_tag, + $no_local, + $no_ack, + $exclusive, + $nowait, + $this->protocolVersion == '0.9.1' ? $arguments : null + ); + + $this->send_method_frame(array($class_id, $method_id), $args); + + if (false === $nowait) { + $consumer_tag = $this->wait(array( + $this->waitHelper->get_wait('basic.consume_ok') + )); + } + + $this->callbacks[$consumer_tag] = $callback; + + return $consumer_tag; + } + + /** + * Confirms a new consumer + * + * @param AMQPReader $args + * @return string + */ + protected function basic_consume_ok($args) + { + return $args->read_shortstr(); + } + + /** + * Notifies the client of a consumer message + * + * @param AMQPReader $args + * @param AMQPMessage $msg + */ + protected function basic_deliver($args, $msg) + { + $consumer_tag = $args->read_shortstr(); + $delivery_tag = $args->read_longlong(); + $redelivered = $args->read_bit(); + $exchange = $args->read_shortstr(); + $routing_key = $args->read_shortstr(); + + $msg->delivery_info = array( + 'channel' => $this, + 'consumer_tag' => $consumer_tag, + 'delivery_tag' => $delivery_tag, + 'redelivered' => $redelivered, + 'exchange' => $exchange, + 'routing_key' => $routing_key + ); + + if (isset($this->callbacks[$consumer_tag])) { + $func = $this->callbacks[$consumer_tag]; + } else { + $func = null; + } + + if ($func != null) { + call_user_func($func, $msg); + } + } + + /** + * Direct access to a queue if no message was available in the queue, return null + * + * @param string $queue + * @param bool $no_ack + * @param null $ticket + * @return mixed + */ + public function basic_get($queue = '', $no_ack = false, $ticket = null) + { + $ticket = $this->getTicket($ticket); + list($class_id, $method_id, $args) = $this->protocolWriter->basicGet($ticket, $queue, $no_ack); + + $this->send_method_frame(array($class_id, $method_id), $args); + + return $this->wait(array( + $this->waitHelper->get_wait('basic.get_ok'), + $this->waitHelper->get_wait('basic.get_empty') + )); + } + + /** + * Indicates no messages available + * + * @param AMQPReader $args + */ + protected function basic_get_empty($args) + { + $cluster_id = $args->read_shortstr(); + } + + /** + * Provides client with a message + * + * @param AMQPReader $args + * @param AMQPMessage $msg + * @return AMQPMessage + */ + protected function basic_get_ok($args, $msg) + { + $delivery_tag = $args->read_longlong(); + $redelivered = $args->read_bit(); + $exchange = $args->read_shortstr(); + $routing_key = $args->read_shortstr(); + $message_count = $args->read_long(); + + $msg->delivery_info = array( + 'delivery_tag' => $delivery_tag, + 'redelivered' => $redelivered, + 'exchange' => $exchange, + 'routing_key' => $routing_key, + 'message_count' => $message_count + ); + + return $msg; + } + + /** + * @param string $exchange + * @param string $routing_key + * @param $mandatory + * @param $immediate + * @param $ticket + * @return mixed + */ + private function pre_publish($exchange, $routing_key, $mandatory, $immediate, $ticket) + { + $cache_key = sprintf( + '%s|%s|%s|%s|%s', + $exchange, + $routing_key, + $mandatory, + $immediate, + $ticket + ); + if (false === isset($this->publish_cache[$cache_key])) { + $ticket = $this->getTicket($ticket); + list($class_id, $method_id, $args) = $this->protocolWriter->basicPublish( + $ticket, + $exchange, + $routing_key, + $mandatory, + $immediate + ); + + $pkt = $this->prepare_method_frame(array($class_id, $method_id), $args); + $this->publish_cache[$cache_key] = $pkt->getvalue(); + if (count($this->publish_cache) > $this->publish_cache_max_size) { + reset($this->publish_cache); + $old_key = key($this->publish_cache); + unset($this->publish_cache[$old_key]); + } + } + + return $this->publish_cache[$cache_key]; + } + + /** + * Publishes a message + * + * @param AMQPMessage $msg + * @param string $exchange + * @param string $routing_key + * @param bool $mandatory + * @param bool $immediate + * @param null $ticket + */ + public function basic_publish( + $msg, + $exchange = '', + $routing_key = '', + $mandatory = false, + $immediate = false, + $ticket = null + ) { + $pkt = new AMQPWriter(); + $pkt->write($this->pre_publish($exchange, $routing_key, $mandatory, $immediate, $ticket)); + + $this->connection->send_content( + $this->channel_id, + 60, + 0, + mb_strlen($msg->body, 'ASCII'), + $msg->serialize_properties(), + $msg->body, + $pkt + ); + + if ($this->next_delivery_tag > 0) { + $this->published_messages[$this->next_delivery_tag] = $msg; + $this->next_delivery_tag = bcadd($this->next_delivery_tag, '1', 0); + } + } + + /** + * @param AMQPMessage $msg + * @param string $exchange + * @param string $routing_key + * @param bool $mandatory + * @param bool $immediate + * @param null $ticket + */ + public function batch_basic_publish( + $msg, + $exchange = '', + $routing_key = '', + $mandatory = false, + $immediate = false, + $ticket = null + ) { + $this->batch_messages[] = func_get_args(); + } + + /** + * Publish batch + * + * @return void + */ + public function publish_batch() + { + if (empty($this->batch_messages)) { + return; + } + + /** @var AMQPWriter $pkt */ + $pkt = new AMQPWriter(); + + /** @var AMQPMessage $msg */ + foreach ($this->batch_messages as $m) { + $msg = $m[0]; + + $exchange = isset($m[1]) ? $m[1] : ''; + $routing_key = isset($m[2]) ? $m[2] : ''; + $mandatory = isset($m[3]) ? $m[3] : false; + $immediate = isset($m[4]) ? $m[4] : false; + $ticket = isset($m[5]) ? $m[5] : null; + $pkt->write($this->pre_publish($exchange, $routing_key, $mandatory, $immediate, $ticket)); + + $this->connection->prepare_content( + $this->channel_id, + 60, + 0, + mb_strlen($msg->body, 'ASCII'), + $msg->serialize_properties(), + $msg->body, + $pkt + ); + + if ($this->next_delivery_tag > 0) { + $this->published_messages[$this->next_delivery_tag] = $msg; + $this->next_delivery_tag = bcadd($this->next_delivery_tag, '1', 0); + } + } + + //call write here + $this->connection->write($pkt->getvalue()); + $this->batch_messages = array(); + } + + /** + * Specifies QoS + * + * @param int $prefetch_size + * @param int $prefetch_count + * @param bool $a_global + * @return mixed + */ + public function basic_qos($prefetch_size, $prefetch_count, $a_global) + { + list($class_id, $method_id, $args) = $this->protocolWriter->basicQos( + $prefetch_size, + $prefetch_count, + $a_global + ); + + $this->send_method_frame(array($class_id, $method_id), $args); + + return $this->wait(array( + $this->waitHelper->get_wait('basic.qos_ok') + )); + } + + /** + * Confirms QoS request + */ + protected function basic_qos_ok($args) + { + } + + /** + * Redelivers unacknowledged messages + * + * @param bool $requeue + * @return mixed + */ + public function basic_recover($requeue = false) + { + list($class_id, $method_id, $args) = $this->protocolWriter->basicRecover($requeue); + $this->send_method_frame(array($class_id, $method_id), $args); + + return $this->wait(array( + $this->waitHelper->get_wait('basic.recover_ok') + )); + } + + /** + * Confirm the requested recover + */ + protected function basic_recover_ok($args) + { + } + + /** + * Rejects an incoming message + * + * @param string $delivery_tag + * @param bool $requeue + */ + public function basic_reject($delivery_tag, $requeue) + { + list($class_id, $method_id, $args) = $this->protocolWriter->basicReject($delivery_tag, $requeue); + $this->send_method_frame(array($class_id, $method_id), $args); + } + + /** + * Returns a failed message + * + * @param AMQPReader $args + * @param AMQPMessage $msg + */ + protected function basic_return($args, $msg) + { + $reply_code = $args->read_short(); + $reply_text = $args->read_shortstr(); + $exchange = $args->read_shortstr(); + $routing_key = $args->read_shortstr(); + + if (null !== ($this->basic_return_callback)) { + call_user_func_array($this->basic_return_callback, array( + $reply_code, + $reply_text, + $exchange, + $routing_key, + $msg, + )); + + } elseif ($this->debug) { + MiscHelper::debug_msg('Skipping unhandled basic_return message'); + } + } + + /** + * @return mixed + */ + public function tx_commit() + { + $this->send_method_frame(array(90, 20)); + + return $this->wait(array( + $this->waitHelper->get_wait('tx.commit_ok') + )); + } + + /** + * Confirms a successful commit + */ + protected function tx_commit_ok($args) + { + } + + /** + * Rollbacks the current transaction + * + * @return mixed + */ + public function tx_rollback() + { + $this->send_method_frame(array(90, 30)); + + return $this->wait(array( + $this->waitHelper->get_wait('tx.rollback_ok') + )); + } + + /** + * Confirms a successful rollback + */ + protected function tx_rollback_ok($args) + { + } + + /** + * Puts the channel into confirm mode + * Beware that only non-transactional channels may be put into confirm mode and vice versa + * + * @param bool $nowait + */ + public function confirm_select($nowait = false) + { + list($class_id, $method_id, $args) = $this->protocolWriter->confirmSelect($nowait); + + $this->send_method_frame(array($class_id, $method_id), $args); + + if ($nowait) { + return null; + } + + $this->wait(array($this->waitHelper->get_wait('confirm.select_ok'))); + $this->next_delivery_tag = 1; + } + + /** + * Confirms a selection + * + * @return void + */ + public function confirm_select_ok() + { + } + + /** + * Waits for pending acks and nacks from the server. + * If there are no pending acks, the method returns immediately + * + * @param int $timeout Waits until $timeout value is reached + */ + public function wait_for_pending_acks($timeout = 0) + { + $functions = array( + $this->waitHelper->get_wait('basic.ack'), + $this->waitHelper->get_wait('basic.nack'), + ); + + while (count($this->published_messages) !== 0) { + if ($timeout > 0) { + $this->wait($functions, true, $timeout); + } else { + $this->wait($functions); + } + } + } + + /** + * Waits for pending acks, nacks and returns from the server. If there are no pending acks, the method returns immediately. + * + * @param int $timeout If set to value > 0 the method will wait at most $timeout seconds for pending acks. + */ + public function wait_for_pending_acks_returns($timeout = 0) + { + $functions = array( + $this->waitHelper->get_wait('basic.ack'), + $this->waitHelper->get_wait('basic.nack'), + $this->waitHelper->get_wait('basic.return'), + ); + + while (count($this->published_messages) !== 0) { + if ($timeout > 0) { + $this->wait($functions, true, $timeout); + } else { + $this->wait($functions); + } + } + } + + /** + * Selects standard transaction mode + * + * @return mixed + */ + public function tx_select() + { + $this->send_method_frame(array(90, 10)); + + return $this->wait(array( + $this->waitHelper->get_wait('tx.select_ok') + )); + } + + /** + * Confirms transaction mode + */ + protected function tx_select_ok($args) + { + } + + /** + * @param mixed $arguments + * @return array|mixed + */ + protected function getArguments($arguments) + { + return (null === $arguments) ? array() : $arguments; + } + + /** + * @param mixed $ticket + * @return int + */ + protected function getTicket($ticket) + { + return (null === $ticket) ? $this->default_ticket : $ticket; + } + + /** + * Helper method to get a particular method from $this->publishedMessages, removes it from the array and returns it. + * + * @param $index + * @return AMQPMessage + */ + protected function get_and_unset_message($index) + { + $message = $this->published_messages[$index]; + unset($this->published_messages[$index]); + + return $message; + } + + /** + * Sets callback for basic_return + * + * @param callable $callback + * @throws \InvalidArgumentException if $callback is not callable + */ + public function set_return_listener($callback) + { + if (false === is_callable($callback)) { + throw new \InvalidArgumentException(sprintf( + 'Given callback "%s" should be callable. %s type was given.', + $callback, + gettype($callback) + )); + } + + $this->basic_return_callback = $callback; + } + + /** + * Sets a handler which called for any message nack'ed by the server, with the AMQPMessage as first argument. + * + * @param callable $callback + * @throws \InvalidArgumentException + */ + public function set_nack_handler($callback) + { + if (false === is_callable($callback)) { + throw new \InvalidArgumentException(sprintf( + 'Given callback "%s" should be callable. %s type was given.', + $callback, + gettype($callback) + )); + } + + $this->nack_handler = $callback; + } + + /** + * Sets a handler which called for any message ack'ed by the server, with the AMQPMessage as first argument. + * + * @param callable $callback + * @throws \InvalidArgumentException + */ + public function set_ack_handler($callback) + { + if (false === is_callable($callback)) { + throw new \InvalidArgumentException(sprintf( + 'Given callback "%s" should be callable. %s type was given.', + $callback, + gettype($callback) + )); + } + + $this->ack_handler = $callback; + } +} diff --git a/lib/PhpAmqpLib/Channel/AbstractChannel.php b/lib/PhpAmqpLib/Channel/AbstractChannel.php new file mode 100644 index 0000000000..fdc02163b0 --- /dev/null +++ b/lib/PhpAmqpLib/Channel/AbstractChannel.php @@ -0,0 +1,419 @@ +connection = $connection; + $this->channel_id = $channel_id; + $connection->channels[$channel_id] = $this; + $this->frame_queue = array(); // Lower level queue for frames + $this->method_queue = array(); // Higher level queue for methods + $this->auto_decode = false; + $this->debug = defined('AMQP_DEBUG') ? AMQP_DEBUG : false; + + $this->msg_property_reader = new AMQPReader(null); + $this->wait_content_reader = new AMQPReader(null); + $this->dispatch_reader = new AMQPReader(null); + + $this->protocolVersion = self::getProtocolVersion(); + switch ($this->protocolVersion) { + case self::PROTOCOL_091: + self::$PROTOCOL_CONSTANTS_CLASS = 'PhpAmqpLib\Wire\Constants091'; + $c = self::$PROTOCOL_CONSTANTS_CLASS; + $this->amqp_protocol_header = $c::$AMQP_PROTOCOL_HEADER; + $this->protocolWriter = new Protocol091(); + $this->waitHelper = new Wait091(); + $this->methodMap = new MethodMap091(); + break; + case self::PROTOCOL_080: + self::$PROTOCOL_CONSTANTS_CLASS = 'PhpAmqpLib\Wire\Constants080'; + $c = self::$PROTOCOL_CONSTANTS_CLASS; + $this->amqp_protocol_header = $c::$AMQP_PROTOCOL_HEADER; + $this->protocolWriter = new Protocol080(); + $this->waitHelper = new Wait080(); + $this->methodMap = new MethodMap080(); + break; + default: + //this is logic exception (requires code changes to fix), so OutOfRange, not OutOfBounds or Runtime + throw new AMQPOutOfRangeException(sprintf('Protocol version %s not implemented.', $this->protocolVersion)); + } + } + + /** + * @return string + * @throws AMQPOutOfRangeException + */ + public static function getProtocolVersion() + { + $protocol = defined('AMQP_PROTOCOL') ? AMQP_PROTOCOL : self::PROTOCOL_091; + //adding check here to catch unknown protocol ASAP, as this method may be called from the outside + if (!in_array($protocol, array(self::PROTOCOL_080, self::PROTOCOL_091), TRUE)) { + throw new AMQPOutOfRangeException(sprintf('Protocol version %s not implemented.', $protocol)); + } + + return $protocol; + } + + /** + * @return string + */ + public function getChannelId() + { + return $this->channel_id; + } + + public function setBodySizeLimit($max_bytes) + { + $max_bytes = intval($max_bytes); + + if ( $max_bytes > 0 ) { + $this->body_size_max = $max_bytes; + } else { + $this->body_size_max = null; + } + } + + /** + * @return AbstractConnection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * @return array + */ + public function getMethodQueue() + { + return $this->method_queue; + } + + /** + * @param string $method_sig + * @param string $args + * @param $content + * @return mixed + * @throws \PhpAmqpLib\Exception\AMQPRuntimeException + */ + public function dispatch($method_sig, $args, $content) + { + if (!$this->methodMap->valid_method($method_sig)) { + throw new AMQPRuntimeException(sprintf( + 'Unknown AMQP method "%s"', + $method_sig + )); + } + + $amqp_method = $this->methodMap->get_method($method_sig); + + if (!method_exists($this, $amqp_method)) { + throw new AMQPRuntimeException(sprintf( + 'Method: "%s" not implemented by class: %s', + $amqp_method, + get_class($this) + )); + } + + $this->dispatch_reader->reuse($args); + + if ($content == null) { + return call_user_func(array($this, $amqp_method), $this->dispatch_reader); + } + + return call_user_func(array($this, $amqp_method), $this->dispatch_reader, $content); + } + + /** + * @param int $timeout + * @return array|mixed + */ + public function next_frame($timeout = 0) + { + if ($this->debug) { + MiscHelper::debug_msg('waiting for a new frame'); + } + + if (!empty($this->frame_queue)) { + return array_shift($this->frame_queue); + } + + return $this->connection->wait_channel($this->channel_id, $timeout); + } + + /** + * @param $method_sig + * @param string $args + */ + protected function send_method_frame($method_sig, $args = '') + { + $this->connection->send_channel_method_frame($this->channel_id, $method_sig, $args); + } + + /** + * This is here for performance reasons to batch calls to fwrite from basic.publish + * + * @param $method_sig + * @param string $args + * @param null $pkt + * @return null|\PhpAmqpLib\Wire\AMQPWriter + */ + protected function prepare_method_frame($method_sig, $args = '', $pkt = null) + { + return $this->connection->prepare_channel_method_frame($this->channel_id, $method_sig, $args, $pkt); + } + + /** + * @return AMQPMessage + * @throws \PhpAmqpLib\Exception\AMQPRuntimeException + */ + public function wait_content() + { + $frm = $this->next_frame(); + $frame_type = $frm[0]; + $payload = $frm[1]; + + if ($frame_type != 2) { + throw new AMQPRuntimeException('Expecting Content header'); + } + + $this->wait_content_reader->reuse(mb_substr($payload, 0, 12, 'ASCII')); + + // $payload_reader = new AMQPReader(substr($payload,0,12)); + $class_id = $this->wait_content_reader->read_short(); + $weight = $this->wait_content_reader->read_short(); + + $body_size = $this->wait_content_reader->read_longlong(); + + //hack to avoid creating new instances of AMQPReader; + $this->msg_property_reader->reuse(mb_substr($payload, 12, mb_strlen($payload, 'ASCII') - 12, 'ASCII')); + + $msg = new AMQPMessage(); + $msg->load_properties($this->msg_property_reader); + $msg->body_size = $body_size; + + $body_parts = array(); + $body_received = 0; + while (bccomp($body_size, $body_received, 0) == 1) { + $frm = $this->next_frame(); + $frame_type = $frm[0]; + $payload = $frm[1]; + + if ($frame_type != 3) { + $PROTOCOL_CONSTANTS_CLASS = self::$PROTOCOL_CONSTANTS_CLASS; + throw new AMQPRuntimeException(sprintf( + 'Expecting Content body, received frame type %s (%s)', + $frame_type, + $PROTOCOL_CONSTANTS_CLASS::$FRAME_TYPES[$frame_type] + )); + } + + $body_received = bcadd($body_received, mb_strlen($payload, 'ASCII'), 0); + + if ( ! is_null($this->body_size_max) && $body_received > $this->body_size_max ) { + $msg->is_truncated = true; + continue; + } + + $body_parts[] = $payload; + } + + $msg->body = implode('', $body_parts); + + if ($this->auto_decode && isset($msg->content_encoding)) { + try { + $msg->body = $msg->body->decode($msg->content_encoding); + } catch (\Exception $e) { + if ($this->debug) { + MiscHelper::debug_msg('Ignoring body decoding exception: ' . $e->getMessage()); + } + } + } + + return $msg; + } + + /** + * Wait for some expected AMQP methods and dispatch to them. + * Unexpected methods are queued up for later calls to this PHP + * method. + * + * @param array $allowed_methods + * @param bool $non_blocking + * @param int $timeout + * @throws \PhpAmqpLib\Exception\AMQPOutOfBoundsException + * @throws \PhpAmqpLib\Exception\AMQPRuntimeException + * @return mixed + */ + public function wait($allowed_methods = null, $non_blocking = false, $timeout = 0) + { + $PROTOCOL_CONSTANTS_CLASS = self::$PROTOCOL_CONSTANTS_CLASS; + + if ($allowed_methods && $this->debug) { + MiscHelper::debug_msg('waiting for ' . implode(', ', $allowed_methods)); + } elseif ($this->debug) { + MiscHelper::debug_msg('waiting for any method'); + } + + //Process deferred methods + foreach ($this->method_queue as $qk => $queued_method) { + if ($this->debug) { + MiscHelper::debug_msg('checking queue method ' . $qk); + } + + $method_sig = $queued_method[0]; + if ($allowed_methods == null || in_array($method_sig, $allowed_methods)) { + unset($this->method_queue[$qk]); + + if ($this->debug) { + MiscHelper::debug_msg(sprintf( + 'Executing queued method: %s: %s', + $method_sig, + $PROTOCOL_CONSTANTS_CLASS::$GLOBAL_METHOD_NAMES[MiscHelper::methodSig($method_sig)] + )); + } + + return $this->dispatch($queued_method[0], $queued_method[1], $queued_method[2]); + } + } + + // No deferred methods? wait for new ones + while (true) { + $frm = $this->next_frame($timeout); + $frame_type = $frm[0]; + $payload = $frm[1]; + + if ($frame_type != 1) { + throw new AMQPRuntimeException(sprintf( + 'Expecting AMQP method, received frame type: %s (%s)', + $frame_type, + $PROTOCOL_CONSTANTS_CLASS::$FRAME_TYPES[$frame_type] + )); + } + + if (mb_strlen($payload, 'ASCII') < 4) { + throw new AMQPOutOfBoundsException('Method frame too short'); + } + + $method_sig_array = unpack('n2', mb_substr($payload, 0, 4, 'ASCII')); + $method_sig = '' . $method_sig_array[1] . ',' . $method_sig_array[2]; + $args = mb_substr($payload, 4, mb_strlen($payload, 'ASCII') - 4, 'ASCII'); + + if ($this->debug) { + MiscHelper::debug_msg(sprintf( + '> %s: %s', + $method_sig, + $PROTOCOL_CONSTANTS_CLASS::$GLOBAL_METHOD_NAMES[MiscHelper::methodSig($method_sig)] + )); + } + + if (in_array($method_sig, $PROTOCOL_CONSTANTS_CLASS::$CONTENT_METHODS)) { + $content = $this->wait_content(); + } else { + $content = null; + } + + if ($allowed_methods == null || + in_array($method_sig, $allowed_methods) || + in_array($method_sig, $PROTOCOL_CONSTANTS_CLASS::$CLOSE_METHODS) + ) { + return $this->dispatch($method_sig, $args, $content); + } + + // Wasn't what we were looking for? save it for later + if ($this->debug) { + MiscHelper::debug_msg('Queueing for later: $method_sig: ' + . $PROTOCOL_CONSTANTS_CLASS::$GLOBAL_METHOD_NAMES[MiscHelper::methodSig($method_sig)]); + } + $this->method_queue[] = array($method_sig, $args, $content); + + if ($non_blocking) { + break; + } + } + } + + /** + * @param $handler + * @param array $arguments + */ + protected function dispatch_to_handler($handler, array $arguments) + { + if (is_callable($handler)) { + call_user_func_array($handler, $arguments); + } + } +} diff --git a/lib/PhpAmqpLib/Connection/AMQPConnection.php b/lib/PhpAmqpLib/Connection/AMQPConnection.php new file mode 100644 index 0000000000..9e4f67a783 --- /dev/null +++ b/lib/PhpAmqpLib/Connection/AMQPConnection.php @@ -0,0 +1,13 @@ +connect(); + + return parent::getSocket(); + } + + /** + * {@inheritdoc} + */ + public function channel($channel_id = null) + { + $this->connect(); + + return parent::channel($channel_id); + } + + /** + * @return null|\PhpAmqpLib\Wire\IO\AbstractIO + */ + protected function getIO() + { + if (!$this->io) { + $this->connect(); + } + + return $this->io; + } + + /** + * Should the connection be attempted during construction? + * + * @return bool + */ + public function connectOnConstruct() + { + return false; + } +} diff --git a/lib/PhpAmqpLib/Connection/AMQPSSLConnection.php b/lib/PhpAmqpLib/Connection/AMQPSSLConnection.php new file mode 100644 index 0000000000..ddfebc2fa2 --- /dev/null +++ b/lib/PhpAmqpLib/Connection/AMQPSSLConnection.php @@ -0,0 +1,56 @@ +create_ssl_context($ssl_options); + parent::__construct( + $host, + $port, + $user, + $password, + $vhost, + isset($options['insist']) ? $options['insist'] : false, + isset($options['login_method']) ? $options['login_method'] : 'AMQPLAIN', + isset($options['login_response']) ? $options['login_response'] : null, + isset($options['locale']) ? $options['locale'] : 'en_US', + isset($options['connection_timeout']) ? $options['connection_timeout'] : 3, + isset($options['read_write_timeout']) ? $options['read_write_timeout'] : 3, + $ssl_context, + isset($options['keepalive']) ? $options['keepalive'] : false, + isset($options['heartbeat']) ? $options['heartbeat'] : 0 + ); + } + + /** + * @param $options + * @return resource + */ + private function create_ssl_context($options) + { + $ssl_context = stream_context_create(); + foreach ($options as $k => $v) { + stream_context_set_option($ssl_context, 'ssl', $k, $v); + } + + return $ssl_context; + } +} diff --git a/lib/PhpAmqpLib/Connection/AMQPSocketConnection.php b/lib/PhpAmqpLib/Connection/AMQPSocketConnection.php new file mode 100644 index 0000000000..54e8814462 --- /dev/null +++ b/lib/PhpAmqpLib/Connection/AMQPSocketConnection.php @@ -0,0 +1,38 @@ +construct_params = func_get_args(); + } +} diff --git a/lib/PhpAmqpLib/Connection/AbstractConnection.php b/lib/PhpAmqpLib/Connection/AbstractConnection.php new file mode 100644 index 0000000000..d2dfe00e21 --- /dev/null +++ b/lib/PhpAmqpLib/Connection/AbstractConnection.php @@ -0,0 +1,972 @@ + array('S', 'AMQPLib'), + 'platform' => array('S', 'PHP'), + 'version' => array('S', '2.4'), + 'information' => array('S', ''), + 'copyright' => array('S', ''), + 'capabilities' => array( + 'F', + array( + 'authentication_failure_close' => array('t', true), + 'publisher_confirms' => array('t', true), + 'consumer_cancel_notify' => array('t', true), + 'exchange_exchange_bindings' => array('t', true), + 'basic.nack' => array('t', true), + 'connection.blocked' => array('t', true) + ) + ) + ); + + /** @var AMQPChannel[] */ + public $channels = array(); + + /** @var int */ + protected $version_major; + + /** @var int */ + protected $version_minor; + + /** @var array */ + protected $server_properties; + + /** @var array */ + protected $mechanisms; + + /** @var array */ + protected $locales; + + /** @var bool */ + protected $wait_tune_ok; + + /** @var string */ + protected $known_hosts; + + /** @var AMQPReader */ + protected $input; + + /** @var string */ + protected $vhost; + + /** @var bool */ + protected $insist; + + /** @var string */ + protected $login_method; + + /** @var AMQPWriter */ + protected $login_response; + + /** @var string */ + protected $locale; + + /** @var int */ + protected $heartbeat; + + /** @var SocketIO */ + protected $sock; + + /** @var int */ + protected $channel_max = 65535; + + /** @var int */ + protected $frame_max = 131072; + + /** @var array Constructor parameters for clone */ + protected $construct_params; + + /** @var bool Close the connection in destructor */ + protected $close_on_destruct = true; + + /** @var bool Maintain connection status */ + protected $is_connected = false; + + /** @var \PhpAmqpLib\Wire\IO\AbstractIO */ + protected $io; + + /** @var \PhpAmqpLib\Wire\AMQPReader */ + protected $wait_frame_reader; + + /** @var callable Handles connection blocking from the server */ + private $connection_block_handler; + + /** @var callable Handles connection unblocking from the server */ + private $connection_unblock_handler; + + /** + * Circular buffer to speed up prepare_content(). + * Max size limited by $prepare_content_cache_max_size. + * + * @var array + * @see prepare_content() + */ + private $prepare_content_cache; + + /** @var int Maximal size of $prepare_content_cache */ + private $prepare_content_cache_max_size; + + /** + * @param AbstractConnection $user + * @param string $password + * @param string $vhost + * @param bool $insist + * @param string $login_method + * @param null $login_response + * @param string $locale + * @param AbstractIO $io + * @param int $heartbeat + * @throws \Exception + */ + public function __construct( + $user, + $password, + $vhost = '/', + $insist = false, + $login_method = 'AMQPLAIN', + $login_response = null, + $locale = 'en_US', + AbstractIO $io, + $heartbeat = 0 + ) { + // save the params for the use of __clone + $this->construct_params = func_get_args(); + + $this->wait_frame_reader = new AMQPReader(null); + $this->vhost = $vhost; + $this->insist = $insist; + $this->login_method = $login_method; + $this->login_response = $login_response; + $this->locale = $locale; + $this->io = $io; + $this->heartbeat = $heartbeat; + + if ($user && $password) { + $this->login_response = new AMQPWriter(); + $this->login_response->write_table(array( + 'LOGIN' => array('S', $user), + 'PASSWORD' => array('S', $password) + )); + + // Skip the length + $responseValue = $this->login_response->getvalue(); + $this->login_response = mb_substr($responseValue, 4, mb_strlen($responseValue, 'ASCII') - 4, 'ASCII'); + + } else { + $this->login_response = null; + } + + $this->prepare_content_cache = array(); + $this->prepare_content_cache_max_size = 100; + + // Lazy Connection waits on connecting + if ($this->connectOnConstruct()) { + $this->connect(); + } + } + + /** + * Connects to the AMQP server + */ + protected function connect() + { + try { + // Loop until we connect + while (!$this->isConnected()) { + // Assume we will connect, until we dont + $this->setIsConnected(true); + + // Connect the socket + $this->getIO()->connect(); + + $this->channels = array(); + // The connection object itself is treated as channel 0 + parent::__construct($this, 0); + + $this->input = new AMQPReader(null, $this->getIO()); + + $this->write($this->amqp_protocol_header); + $this->wait(array($this->waitHelper->get_wait('connection.start'))); + $this->x_start_ok(self::$LIBRARY_PROPERTIES, $this->login_method, $this->login_response, $this->locale); + + $this->wait_tune_ok = true; + while ($this->wait_tune_ok) { + $this->wait(array( + $this->waitHelper->get_wait('connection.secure'), + $this->waitHelper->get_wait('connection.tune') + )); + } + + $host = $this->x_open($this->vhost, '', $this->insist); + if (!$host) { + return; // we weren't redirected + } + + $this->setIsConnected(false); + $this->closeChannels(); + + // we were redirected, close the socket, loop and try again + $this->close_socket(); + } + + } catch (\Exception $e) { + // Something went wrong, set the connection status + $this->setIsConnected(false); + $this->closeChannels(); + throw $e; // Rethrow exception + } + } + + /** + * Reconnects using the original connection settings. + * This will not recreate any channels that were established previously + */ + public function reconnect() + { + // Try to close the AMQP connection + $this->safeClose(); + // Reconnect the socket/stream then AMQP + $this->getIO()->reconnect(); + $this->setIsConnected(false); // getIO can initiate the connection setting via LazyConnection, set it here to be sure + $this->connect(); + } + + /** + * Cloning will use the old properties to make a new connection to the same server + */ + public function __clone() + { + call_user_func_array(array($this, '__construct'), $this->construct_params); + } + + public function __destruct() + { + if ($this->close_on_destruct) { + $this->safeClose(); + } + } + + /** + * Attempts to close the connection safely + */ + protected function safeClose() + { + try { + if (isset($this->input) && $this->input) { + $this->close(); + } + } catch (\Exception $e) { + // Nothing here + } + } + + /** + * @param int $sec + * @param int $usec + * @return mixed + */ + public function select($sec, $usec = 0) + { + return $this->getIO()->select($sec, $usec); + } + + /** + * Allows to not close the connection + * it's useful after the fork when you don't want to close parent process connection + * + * @param bool $close + */ + public function set_close_on_destruct($close = true) + { + $this->close_on_destruct = (bool) $close; + } + + protected function close_input() + { + if ($this->debug) { + MiscHelper::debug_msg('closing input'); + } + + if (!is_null($this->input)) { + $this->input->close(); + $this->input = null; + } + } + + protected function close_socket() + { + if ($this->debug) { + MiscHelper::debug_msg('closing socket'); + } + + if (!is_null($this->getIO())) { + $this->getIO()->close(); + } + } + + /** + * @param $data + */ + public function write($data) + { + if ($this->debug) { + MiscHelper::debug_msg(sprintf( + '< [hex]: %s%s', + PHP_EOL, + MiscHelper::hexdump($data, $htmloutput = false, $uppercase = true, $return = true) + )); + } + + try { + $this->getIO()->write($data); + } catch (AMQPRuntimeException $e) { + $this->setIsConnected(false); + throw $e; + } + } + + protected function do_close() + { + $this->setIsConnected(false); + $this->close_input(); + $this->close_socket(); + } + + /** + * @return int + * @throws \PhpAmqpLib\Exception\AMQPRuntimeException + */ + public function get_free_channel_id() + { + for ($i = 1; $i <= $this->channel_max; $i++) { + if (!isset($this->channels[$i])) { + return $i; + } + } + + throw new AMQPRuntimeException('No free channel ids'); + } + + /** + * @param string $channel + * @param int $class_id + * @param int $weight + * @param int $body_size + * @param string $packed_properties + * @param string $body + * @param AMQPWriter $pkt + */ + public function send_content($channel, $class_id, $weight, $body_size, $packed_properties, $body, $pkt = null) + { + $this->prepare_content($channel, $class_id, $weight, $body_size, $packed_properties, $body, $pkt); + $this->write($pkt->getvalue()); + } + + /** + * Returns a new AMQPWriter or mutates the provided $pkt + * + * @param string $channel + * @param int $class_id + * @param int $weight + * @param int $body_size + * @param string $packed_properties + * @param string $body + * @param AMQPWriter $pkt + * @return AMQPWriter + */ + public function prepare_content($channel, $class_id, $weight, $body_size, $packed_properties, $body, $pkt = null) + { + if (empty($pkt)) { + $pkt = new AMQPWriter(); + } + + // Content already prepared ? + $key_cache = sprintf( + '%s|%s|%s|%s', + $channel, + $packed_properties, + $class_id, + $weight + ); + if (!isset($this->prepare_content_cache[$key_cache])) { + $w = new AMQPWriter(); + $w->write_octet(2); + $w->write_short($channel); + $w->write_long(mb_strlen($packed_properties, 'ASCII') + 12); + $w->write_short($class_id); + $w->write_short($weight); + $this->prepare_content_cache[$key_cache] = $w->getvalue(); + if (count($this->prepare_content_cache) > $this->prepare_content_cache_max_size) { + reset($this->prepare_content_cache); + $old_key = key($this->prepare_content_cache); + unset($this->prepare_content_cache[$old_key]); + } + } + $pkt->write($this->prepare_content_cache[$key_cache]); + + $pkt->write_longlong($body_size); + $pkt->write($packed_properties); + + $pkt->write_octet(0xCE); + + + // memory efficiency: walk the string instead of biting it. good for very large packets (close in size to memory_limit setting) + $position = 0; + $bodyLength = mb_strlen($body,'ASCII'); + while ($position <= $bodyLength) { + $payload = mb_substr($body, $position, $this->frame_max - 8, 'ASCII'); + $position += $this->frame_max - 8; + + $pkt->write_octet(3); + $pkt->write_short($channel); + $pkt->write_long(mb_strlen($payload, 'ASCII')); + + $pkt->write($payload); + + $pkt->write_octet(0xCE); + } + + return $pkt; + } + + /** + * @param $channel + * @param $method_sig + * @param string $args + * @param null $pkt + */ + protected function send_channel_method_frame($channel, $method_sig, $args = '', $pkt = null) + { + $pkt = $this->prepare_channel_method_frame($channel, $method_sig, $args, $pkt); + + $this->write($pkt->getvalue()); + + if ($this->debug) { + $PROTOCOL_CONSTANTS_CLASS = self::$PROTOCOL_CONSTANTS_CLASS; + MiscHelper::debug_msg(sprintf( + '< %s: %s', + MiscHelper::methodSig($method_sig), + $PROTOCOL_CONSTANTS_CLASS::$GLOBAL_METHOD_NAMES[MiscHelper::methodSig($method_sig)] + )); + } + } + + /** + * Returns a new AMQPWriter or mutates the provided $pkt + * + * @param $channel + * @param $method_sig + * @param string $args + * @param AMQPWriter $pkt + * @return null|AMQPWriter + */ + protected function prepare_channel_method_frame($channel, $method_sig, $args = '', $pkt = null) + { + if ($args instanceof AMQPWriter) { + $args = $args->getvalue(); + } + + if (empty($pkt)) { + $pkt = new AMQPWriter(); + } + + $pkt->write_octet(1); + $pkt->write_short($channel); + $pkt->write_long(mb_strlen($args, 'ASCII') + 4); // 4 = length of class_id and method_id + // in payload + + $pkt->write_short($method_sig[0]); // class_id + $pkt->write_short($method_sig[1]); // method_id + $pkt->write($args); + + $pkt->write_octet(0xCE); + + if ($this->debug) { + $PROTOCOL_CONSTANTS_CLASS = self::$PROTOCOL_CONSTANTS_CLASS; + MiscHelper::debug_msg(sprintf( + '< %s: %s', + MiscHelper::methodSig($method_sig), + $PROTOCOL_CONSTANTS_CLASS::$GLOBAL_METHOD_NAMES[MiscHelper::methodSig($method_sig)] + )); + } + + return $pkt; + } + + /** + * Waits for a frame from the server + * + * @param int $timeout + * @return array + * @throws \Exception + * @throws \PhpAmqpLib\Exception\AMQPTimeoutException + * @throws \PhpAmqpLib\Exception\AMQPRuntimeException + */ + protected function wait_frame($timeout = 0) + { + $currentTimeout = $this->input->getTimeout(); + $this->input->setTimeout($timeout); + + try { + // frame_type + channel_id + size + $this->wait_frame_reader->reuse( + $this->input->read(AMQPReader::OCTET + AMQPReader::SHORT + AMQPReader::LONG) + ); + + $frame_type = $this->wait_frame_reader->read_octet(); + $channel = $this->wait_frame_reader->read_short(); + $size = $this->wait_frame_reader->read_long(); + + // payload + ch + $this->wait_frame_reader->reuse($this->input->read(AMQPReader::OCTET + (int) $size)); + + $payload = $this->wait_frame_reader->read($size); + $ch = $this->wait_frame_reader->read_octet(); + + } catch (AMQPTimeoutException $e) { + $this->input->setTimeout($currentTimeout); + throw $e; + } + + $this->input->setTimeout($currentTimeout); + + if ($ch != 0xCE) { + throw new AMQPRuntimeException(sprintf( + 'Framing error, unexpected byte: %x', + $ch + )); + } + + return array($frame_type, $channel, $payload); + } + + /** + * Waits for a frame from the server destined for a particular channel. + * + * @param string $channel_id + * @param int $timeout + * @return array + */ + protected function wait_channel($channel_id, $timeout = 0) + { + while (true) { + list($frame_type, $frame_channel, $payload) = $this->wait_frame($timeout); + + if ($frame_channel === 0 && $frame_type === 8) { + // skip heartbeat frames + continue; + + } else { + + if ($frame_channel == $channel_id) { + return array($frame_type, $payload); + } + + // Not the channel we were looking for. Queue this frame + //for later, when the other channel is looking for frames. + array_push($this->channels[$frame_channel]->frame_queue, array($frame_type, $payload)); + + // If we just queued up a method for channel 0 (the Connection + // itself) it's probably a close method in reaction to some + // error, so deal with it right away. + if (($frame_type == 1) && ($frame_channel == 0)) { + $this->wait(); + } + } + } + } + + /** + * Fetches a channel object identified by the numeric channel_id, or + * create that object if it doesn't already exist. + * + * @param string $channel_id + * @return AMQPChannel + */ + public function channel($channel_id = null) + { + if (isset($this->channels[$channel_id])) { + return $this->channels[$channel_id]; + } else { + $channel_id = $channel_id ? $channel_id : $this->get_free_channel_id(); + $ch = new AMQPChannel($this->connection, $channel_id); + $this->channels[$channel_id] = $ch; + + return $ch; + } + } + + /** + * Requests a connection close + * + * @param int $reply_code + * @param string $reply_text + * @param array $method_sig + * @return mixed|null + */ + public function close($reply_code = 0, $reply_text = '', $method_sig = array(0, 0)) + { + if (!$this->protocolWriter || !$this->isConnected()) { + return null; + } + + $this->closeChannels(); + + list($class_id, $method_id, $args) = $this->protocolWriter->connectionClose( + $reply_code, + $reply_text, + $method_sig[0], + $method_sig[1] + ); + $this->send_method_frame(array($class_id, $method_id), $args); + + $this->setIsConnected(false); + + return $this->wait(array( + $this->waitHelper->get_wait('connection.close_ok') + )); + } + + /** + * @param $table + * @return string + */ + public static function dump_table($table) + { + $tokens = array(); + foreach ($table as $name => $value) { + switch ($value[0]) { + case 'D': + $val = $value[1]->n . 'E' . $value[1]->e; + break; + case 'F': + $val = '(' . self::dump_table($value[1]) . ')'; + break; + case 'T': + $val = date('Y-m-d H:i:s', $value[1]); + break; + default: + $val = $value[1]; + } + $tokens[] = $name . '=' . $val; + } + + return implode(', ', $tokens); + } + + /** + * @param AMQPReader $args + * @throws \PhpAmqpLib\Exception\AMQPProtocolConnectionException + */ + protected function connection_close($args) + { + $reply_code = $args->read_short(); + $reply_text = $args->read_shortstr(); + $class_id = $args->read_short(); + $method_id = $args->read_short(); + + $this->x_close_ok(); + + throw new AMQPProtocolConnectionException($reply_code, $reply_text, array($class_id, $method_id)); + } + + /** + * Confirms a connection close + */ + protected function x_close_ok() + { + $this->send_method_frame( + explode(',', $this->waitHelper->get_wait('connection.close_ok')) + ); + $this->do_close(); + } + + /** + * Confirm a connection close + */ + protected function connection_close_ok($args) + { + $this->do_close(); + } + + /** + * @param string $virtual_host + * @param string $capabilities + * @param bool $insist + * @return mixed + */ + protected function x_open($virtual_host, $capabilities = '', $insist = false) + { + $args = new AMQPWriter(); + $args->write_shortstr($virtual_host); + $args->write_shortstr($capabilities); + $args->write_bits(array($insist)); + $this->send_method_frame(array(10, 40), $args); + + $wait = array( + $this->waitHelper->get_wait('connection.open_ok') + ); + + if ($this->protocolVersion == '0.8') { + $wait[] = $this->waitHelper->get_wait('connection.redirect'); + } + + return $this->wait($wait); + } + + /** + * Signals that the connection is ready + * + * @param AMQPReader $args + */ + protected function connection_open_ok($args) + { + $this->known_hosts = $args->read_shortstr(); + if ($this->debug) { + MiscHelper::debug_msg('Open OK! known_hosts: ' . $this->known_hosts); + } + } + + /** + * Asks the client to use a different server + * + * @param AMQPReader $args + * @return string + */ + protected function connection_redirect($args) + { + $host = $args->read_shortstr(); + $this->known_hosts = $args->read_shortstr(); + if ($this->debug) { + MiscHelper::debug_msg(sprintf( + 'Redirected to [%s], known_hosts [%s]', + $host, + $this->known_hosts + )); + } + + return $host; + } + + /** + * Security mechanism challenge + * + * @param AMQPReader $args + */ + protected function connection_secure($args) + { + $challenge = $args->read_longstr(); + } + + /** + * Security mechanism response + */ + protected function x_secure_ok($response) + { + $args = new AMQPWriter(); + $args->write_longstr($response); + $this->send_method_frame(array(10, 21), $args); + } + + /** + * Starts connection negotiation + * + * @param AMQPReader $args + */ + protected function connection_start($args) + { + $this->version_major = $args->read_octet(); + $this->version_minor = $args->read_octet(); + $this->server_properties = $args->read_table(); + $this->mechanisms = explode(' ', $args->read_longstr()); + $this->locales = explode(' ', $args->read_longstr()); + + if ($this->debug) { + MiscHelper::debug_msg(sprintf( + 'Start from server, version: %d.%d, properties: %s, mechanisms: %s, locales: %s', + $this->version_major, + $this->version_minor, + self::dump_table($this->server_properties), + implode(', ', $this->mechanisms), + implode(', ', $this->locales) + )); + } + } + + /** + * @param $client_properties + * @param $mechanism + * @param $response + * @param $locale + */ + protected function x_start_ok($client_properties, $mechanism, $response, $locale) + { + $args = new AMQPWriter(); + $args->write_table($client_properties); + $args->write_shortstr($mechanism); + $args->write_longstr($response); + $args->write_shortstr($locale); + $this->send_method_frame(array(10, 11), $args); + } + + /** + * Proposes connection tuning parameters + * + * @param AMQPReader $args + */ + protected function connection_tune($args) + { + $v = $args->read_short(); + if ($v) { + $this->channel_max = $v; + } + + $v = $args->read_long(); + if ($v) { + $this->frame_max = $v; + } + + // use server proposed value if not set + if ($this->heartbeat === null) { + $this->heartbeat = $args->read_short(); + } + + $this->x_tune_ok($this->channel_max, $this->frame_max, $this->heartbeat); + } + + /** + * Negotiates connection tuning parameters + * + * @param $channel_max + * @param $frame_max + * @param $heartbeat + */ + protected function x_tune_ok($channel_max, $frame_max, $heartbeat) + { + $args = new AMQPWriter(); + $args->write_short($channel_max); + $args->write_long($frame_max); + $args->write_short($heartbeat); + $this->send_method_frame(array(10, 31), $args); + $this->wait_tune_ok = false; + } + + /** + * @return SocketIO + */ + public function getSocket() + { + return $this->io->getSocket(); + } + + /** + * @return \PhpAmqpLib\Wire\IO\AbstractIO + */ + protected function getIO() + { + return $this->io; + } + + /** + * Handles connection blocked notifications + * + * @param AMQPReader $args + */ + protected function connection_blocked(AMQPReader $args) + { + // Call the block handler and pass in the reason + $this->dispatch_to_handler($this->connection_block_handler, array($args->read_shortstr())); + } + + /** + * Handles connection unblocked notifications + */ + protected function connection_unblocked(AMQPReader $args) + { + // No args to an unblock event + $this->dispatch_to_handler($this->connection_unblock_handler, array()); + } + + /** + * Sets a handler which is called whenever a connection.block is sent from the server + * + * @param callable $callback + */ + public function set_connection_block_handler($callback) + { + $this->connection_block_handler = $callback; + } + + /** + * Sets a handler which is called whenever a connection.block is sent from the server + * + * @param callable $callback + */ + public function set_connection_unblock_handler($callback) + { + $this->connection_unblock_handler = $callback; + } + + /** + * Gets the connection status + * + * @return bool + */ + public function isConnected() + { + return $this->is_connected; + } + + /** + * Set the connection status + * + * @param bool $is_connected + */ + protected function setIsConnected($is_connected) + { + $this->is_connected = $is_connected; + } + + /** + * Closes all available channels + */ + protected function closeChannels() + { + foreach ($this->channels as $key => $channel) { + // channels[0] is this connection object, so don't close it yet + if ($key === 0) { + continue; + } + try { + $channel->close(); + } catch (\Exception $e) { + /* Ignore closing errors */ + } + } + } + + /** + * Should the connection be attempted during construction? + * + * @return bool + */ + public function connectOnConstruct() + { + return true; + } +} diff --git a/lib/PhpAmqpLib/Exception/AMQPBasicCancelException.php b/lib/PhpAmqpLib/Exception/AMQPBasicCancelException.php new file mode 100644 index 0000000000..51d7b3e970 --- /dev/null +++ b/lib/PhpAmqpLib/Exception/AMQPBasicCancelException.php @@ -0,0 +1,16 @@ +consumerTag = $consumerTag; + } +} diff --git a/lib/PhpAmqpLib/Exception/AMQPChannelException.php b/lib/PhpAmqpLib/Exception/AMQPChannelException.php new file mode 100644 index 0000000000..b6ca39e5d1 --- /dev/null +++ b/lib/PhpAmqpLib/Exception/AMQPChannelException.php @@ -0,0 +1,9 @@ +amqp_reply_code = $reply_code; // redundant, but kept for BC + $this->amqp_reply_text = $reply_text; // redundant, but kept for BC + $this->amqp_method_sig = $method_sig; + + $ms = MiscHelper::methodSig($method_sig); + $PROTOCOL_CONSTANTS_CLASS = AbstractChannel::$PROTOCOL_CONSTANTS_CLASS; + $mn = isset($PROTOCOL_CONSTANTS_CLASS::$GLOBAL_METHOD_NAMES[$ms]) + ? $PROTOCOL_CONSTANTS_CLASS::$GLOBAL_METHOD_NAMES[$ms] + : $mn = ''; + + $this->args = array($reply_code, $reply_text, $method_sig, $mn); + } +} diff --git a/lib/PhpAmqpLib/Exception/AMQPExceptionInterface.php b/lib/PhpAmqpLib/Exception/AMQPExceptionInterface.php new file mode 100644 index 0000000000..74d61992dd --- /dev/null +++ b/lib/PhpAmqpLib/Exception/AMQPExceptionInterface.php @@ -0,0 +1,6 @@ +amqp_reply_code = $reply_code; // redundant, but kept for BC + $this->amqp_reply_text = $reply_text; // redundant, but kept for BC + $this->amqp_method_sig = $method_sig; + + $ms = MiscHelper::methodSig($method_sig); + + $PROTOCOL_CONSTANTS_CLASS = AbstractChannel::$PROTOCOL_CONSTANTS_CLASS; + $mn = isset($PROTOCOL_CONSTANTS_CLASS::$GLOBAL_METHOD_NAMES[$ms]) + ? $PROTOCOL_CONSTANTS_CLASS::$GLOBAL_METHOD_NAMES[$ms] + : $mn = ''; + + $this->args = array($reply_code, $reply_text, $method_sig, $mn); + } +} diff --git a/lib/PhpAmqpLib/Exception/AMQPRuntimeException.php b/lib/PhpAmqpLib/Exception/AMQPRuntimeException.php new file mode 100644 index 0000000000..ef0dcff063 --- /dev/null +++ b/lib/PhpAmqpLib/Exception/AMQPRuntimeException.php @@ -0,0 +1,6 @@ + + * @author Peter Waller + * @link http://aidanlister.com/repos/v/function.hexdump.php + * + * @param string $data The string to be dumped + * @param bool $htmloutput Set to false for non-HTML output + * @param bool $uppercase Set to true for uppercase hex + * @param bool $return Set to true to return the dump + * @return string + */ + public static function hexdump($data, $htmloutput = true, $uppercase = false, $return = false) + { + // Init + $hexi = ''; + $ascii = ''; + $dump = $htmloutput ? '
' : '';
+        $offset = 0;
+        $len = mb_strlen($data, 'ASCII');
+
+        // Upper or lower case hexidecimal
+        $hexFormat = $uppercase ? 'X' : 'x';
+
+        // Iterate string
+        for ($i = $j = 0; $i < $len; $i++) {
+            // Convert to hexidecimal
+            // We must use concatenation here because the $hexFormat value
+            // is needed for sprintf() to parse the format
+            $hexi .= sprintf('%02' .  $hexFormat . ' ', ord($data[$i]));
+
+            // Replace non-viewable bytes with '.'
+            if (ord($data[$i]) >= 32) {
+                $ascii .= $htmloutput ? htmlentities($data[$i]) : $data[$i];
+            } else {
+                $ascii .= '.';
+            }
+
+            // Add extra column spacing
+            if ($j === 7) {
+                $hexi .= ' ';
+                $ascii .= ' ';
+            }
+
+            // Add row
+            if (++$j === 16 || $i === $len - 1) {
+                // Join the hexi / ascii output
+                // We must use concatenation here because the $hexFormat value
+                // is needed for sprintf() to parse the format
+                $dump .= sprintf('%04' . $hexFormat . '  %-49s  %s', $offset, $hexi, $ascii);
+
+                // Reset vars
+                $hexi = $ascii = '';
+                $offset += 16;
+                $j = 0;
+
+                // Add newline
+                if ($i !== $len - 1) {
+                    $dump .= PHP_EOL;
+                }
+            }
+        }
+
+        // Finish dump
+        $dump .= $htmloutput ? '
' : ''; + $dump .= PHP_EOL; + + if ($return) { + return $dump; + } + + echo $dump; + } +} diff --git a/lib/PhpAmqpLib/Helper/Protocol/MethodMap080.php b/lib/PhpAmqpLib/Helper/Protocol/MethodMap080.php new file mode 100644 index 0000000000..a43c3f27d7 --- /dev/null +++ b/lib/PhpAmqpLib/Helper/Protocol/MethodMap080.php @@ -0,0 +1,120 @@ + 'connection_start', + '10,11' => 'connection_start_ok', + '10,20' => 'connection_secure', + '10,21' => 'connection_secure_ok', + '10,30' => 'connection_tune', + '10,31' => 'connection_tune_ok', + '10,40' => 'connection_open', + '10,41' => 'connection_open_ok', + '10,50' => 'connection_redirect', + '10,60' => 'connection_close', + '10,61' => 'connection_close_ok', + '20,10' => 'channel_open', + '20,11' => 'channel_open_ok', + '20,20' => 'channel_flow', + '20,21' => 'channel_flow_ok', + '20,30' => 'channel_alert', + '20,40' => 'channel_close', + '20,41' => 'channel_close_ok', + '30,10' => 'access_request', + '30,11' => 'access_request_ok', + '40,10' => 'exchange_declare', + '40,11' => 'exchange_declare_ok', + '40,20' => 'exchange_delete', + '40,21' => 'exchange_delete_ok', + '50,10' => 'queue_declare', + '50,11' => 'queue_declare_ok', + '50,20' => 'queue_bind', + '50,21' => 'queue_bind_ok', + '50,30' => 'queue_purge', + '50,31' => 'queue_purge_ok', + '50,40' => 'queue_delete', + '50,41' => 'queue_delete_ok', + '50,50' => 'queue_unbind', + '50,51' => 'queue_unbind_ok', + '60,10' => 'basic_qos', + '60,11' => 'basic_qos_ok', + '60,20' => 'basic_consume', + '60,21' => 'basic_consume_ok', + '60,30' => 'basic_cancel', + '60,31' => 'basic_cancel_ok', + '60,40' => 'basic_publish', + '60,50' => 'basic_return', + '60,60' => 'basic_deliver', + '60,70' => 'basic_get', + '60,71' => 'basic_get_ok', + '60,72' => 'basic_get_empty', + '60,80' => 'basic_ack', + '60,90' => 'basic_reject', + '60,100' => 'basic_recover_async', + '60,110' => 'basic_recover', + '60,111' => 'basic_recover_ok', + '70,10' => 'file_qos', + '70,11' => 'file_qos_ok', + '70,20' => 'file_consume', + '70,21' => 'file_consume_ok', + '70,30' => 'file_cancel', + '70,31' => 'file_cancel_ok', + '70,40' => 'file_open', + '70,41' => 'file_open_ok', + '70,50' => 'file_stage', + '70,60' => 'file_publish', + '70,70' => 'file_return', + '70,80' => 'file_deliver', + '70,90' => 'file_ack', + '70,100' => 'file_reject', + '80,10' => 'stream_qos', + '80,11' => 'stream_qos_ok', + '80,20' => 'stream_consume', + '80,21' => 'stream_consume_ok', + '80,30' => 'stream_cancel', + '80,31' => 'stream_cancel_ok', + '80,40' => 'stream_publish', + '80,50' => 'stream_return', + '80,60' => 'stream_deliver', + '90,10' => 'tx_select', + '90,11' => 'tx_select_ok', + '90,20' => 'tx_commit', + '90,21' => 'tx_commit_ok', + '90,30' => 'tx_rollback', + '90,31' => 'tx_rollback_ok', + '100,10' => 'dtx_select', + '100,11' => 'dtx_select_ok', + '100,20' => 'dtx_start', + '100,21' => 'dtx_start_ok', + '110,10' => 'tunnel_request', + '120,10' => 'test_integer', + '120,11' => 'test_integer_ok', + '120,20' => 'test_string', + '120,21' => 'test_string_ok', + '120,30' => 'test_table', + '120,31' => 'test_table_ok', + '120,40' => 'test_content', + '120,41' => 'test_content_ok', + ); + + + + public function get_method($method_sig) + { + return $this->method_map[$method_sig]; + } + + + + public function valid_method($method_sig) + { + return array_key_exists($method_sig, $this->method_map); + } + +} diff --git a/lib/PhpAmqpLib/Helper/Protocol/MethodMap091.php b/lib/PhpAmqpLib/Helper/Protocol/MethodMap091.php new file mode 100644 index 0000000000..e3c4e79994 --- /dev/null +++ b/lib/PhpAmqpLib/Helper/Protocol/MethodMap091.php @@ -0,0 +1,91 @@ + 'connection_start', + '10,11' => 'connection_start_ok', + '10,20' => 'connection_secure', + '10,21' => 'connection_secure_ok', + '10,30' => 'connection_tune', + '10,31' => 'connection_tune_ok', + '10,40' => 'connection_open', + '10,41' => 'connection_open_ok', + '10,50' => 'connection_close', + '10,51' => 'connection_close_ok', + '10,60' => 'connection_blocked', + '10,61' => 'connection_unblocked', + '20,10' => 'channel_open', + '20,11' => 'channel_open_ok', + '20,20' => 'channel_flow', + '20,21' => 'channel_flow_ok', + '20,40' => 'channel_close', + '20,41' => 'channel_close_ok', + '30,10' => 'access_request', + '30,11' => 'access_request_ok', + '40,10' => 'exchange_declare', + '40,11' => 'exchange_declare_ok', + '40,20' => 'exchange_delete', + '40,21' => 'exchange_delete_ok', + '40,30' => 'exchange_bind', + '40,31' => 'exchange_bind_ok', + '40,40' => 'exchange_unbind', + '40,51' => 'exchange_unbind_ok', + '50,10' => 'queue_declare', + '50,11' => 'queue_declare_ok', + '50,20' => 'queue_bind', + '50,21' => 'queue_bind_ok', + '50,30' => 'queue_purge', + '50,31' => 'queue_purge_ok', + '50,40' => 'queue_delete', + '50,41' => 'queue_delete_ok', + '50,50' => 'queue_unbind', + '50,51' => 'queue_unbind_ok', + '60,10' => 'basic_qos', + '60,11' => 'basic_qos_ok', + '60,20' => 'basic_consume', + '60,21' => 'basic_consume_ok', + '60,30' => 'basic_cancel_from_server', + '60,31' => 'basic_cancel_ok', + '60,40' => 'basic_publish', + '60,50' => 'basic_return', + '60,60' => 'basic_deliver', + '60,70' => 'basic_get', + '60,71' => 'basic_get_ok', + '60,72' => 'basic_get_empty', + '60,80' => 'basic_ack_from_server', + '60,90' => 'basic_reject', + '60,100' => 'basic_recover_async', + '60,110' => 'basic_recover', + '60,111' => 'basic_recover_ok', + '60,120' => 'basic_nack_from_server', + '90,10' => 'tx_select', + '90,11' => 'tx_select_ok', + '90,20' => 'tx_commit', + '90,21' => 'tx_commit_ok', + '90,30' => 'tx_rollback', + '90,31' => 'tx_rollback_ok', + '85,10' => 'confirm_select', + '85,11' => 'confirm_select_ok', + ); + + + + public function get_method($method_sig) + { + return $this->method_map[$method_sig]; + } + + + + public function valid_method($method_sig) + { + return array_key_exists($method_sig, $this->method_map); + } + +} diff --git a/lib/PhpAmqpLib/Helper/Protocol/Protocol080.php b/lib/PhpAmqpLib/Helper/Protocol/Protocol080.php new file mode 100644 index 0000000000..f22a94e947 --- /dev/null +++ b/lib/PhpAmqpLib/Helper/Protocol/Protocol080.php @@ -0,0 +1,1260 @@ +write_octet($version_major); + $args->write_octet($version_minor); + $args->write_table(empty($server_properties) ? array() : $server_properties); + $args->write_longstr($mechanisms); + $args->write_longstr($locales); + return array(10, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function connectionStartOk($args) + { + $ret = array(); + $ret[] = $args->read_table(); + $ret[] = $args->read_shortstr(); + $ret[] = $args->read_longstr(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function connectionSecure($challenge) + { + $args = new AMQPWriter(); + $args->write_longstr($challenge); + return array(10, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function connectionSecureOk($args) + { + $ret = array(); + $ret[] = $args->read_longstr(); + return $ret; + } + + + + /** + * @return array + */ + public function connectionTune($channel_max = 0, $frame_max = 0, $heartbeat = 0) + { + $args = new AMQPWriter(); + $args->write_short($channel_max); + $args->write_long($frame_max); + $args->write_short($heartbeat); + return array(10, 30, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function connectionTuneOk($args) + { + $ret = array(); + $ret[] = $args->read_short(); + $ret[] = $args->read_long(); + $ret[] = $args->read_short(); + return $ret; + } + + + + /** + * @return array + */ + public function connectionOpen($virtual_host = '/', $capabilities = '', $insist = false) + { + $args = new AMQPWriter(); + $args->write_shortstr($virtual_host); + $args->write_shortstr($capabilities); + $args->write_bits(array($insist)); + return array(10, 40, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function connectionOpenOk($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function connectionRedirect($host, $known_hosts = '') + { + $args = new AMQPWriter(); + $args->write_shortstr($host); + $args->write_shortstr($known_hosts); + return array(10, 50, $args); + } + + + + /** + * @return array + */ + public function connectionClose($reply_code, $reply_text = '', $class_id, $method_id) + { + $args = new AMQPWriter(); + $args->write_short($reply_code); + $args->write_shortstr($reply_text); + $args->write_short($class_id); + $args->write_short($method_id); + return array(10, 60, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function connectionCloseOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function channelOpen($out_of_band = '') + { + $args = new AMQPWriter(); + $args->write_shortstr($out_of_band); + return array(20, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function channelOpenOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function channelFlow($active) + { + $args = new AMQPWriter(); + $args->write_bits(array($active)); + return array(20, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function channelFlowOk($args) + { + $ret = array(); + $ret[] = $args->read_bit(); + return $ret; + } + + + + /** + * @return array + */ + public function channelAlert($reply_code, $reply_text = '', $details = array()) + { + $args = new AMQPWriter(); + $args->write_short($reply_code); + $args->write_shortstr($reply_text); + $args->write_table(empty($details) ? array() : $details); + return array(20, 30, $args); + } + + + + /** + * @return array + */ + public function channelClose($reply_code, $reply_text = '', $class_id, $method_id) + { + $args = new AMQPWriter(); + $args->write_short($reply_code); + $args->write_shortstr($reply_text); + $args->write_short($class_id); + $args->write_short($method_id); + return array(20, 40, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function channelCloseOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function accessRequest($realm = '/data', $exclusive = false, $passive = true, $active = true, $write = true, $read = true) + { + $args = new AMQPWriter(); + $args->write_shortstr($realm); + $args->write_bits(array($exclusive, $passive, $active, $write, $read)); + return array(30, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function accessRequestOk($args) + { + $ret = array(); + $ret[] = $args->read_short(); + return $ret; + } + + + + /** + * @return array + */ + public function exchangeDeclare($ticket = 1, $exchange, $type = 'direct', $passive = false, $durable = false, $auto_delete = false, $internal = false, $nowait = false, $arguments = array()) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($exchange); + $args->write_shortstr($type); + $args->write_bits(array($passive, $durable, $auto_delete, $internal, $nowait)); + $args->write_table(empty($arguments) ? array() : $arguments); + return array(40, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function exchangeDeclareOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function exchangeDelete($ticket = 1, $exchange, $if_unused = false, $nowait = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($exchange); + $args->write_bits(array($if_unused, $nowait)); + return array(40, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function exchangeDeleteOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function queueDeclare($ticket = 1, $queue = '', $passive = false, $durable = false, $exclusive = false, $auto_delete = false, $nowait = false, $arguments = array()) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_bits(array($passive, $durable, $exclusive, $auto_delete, $nowait)); + $args->write_table(empty($arguments) ? array() : $arguments); + return array(50, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function queueDeclareOk($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + $ret[] = $args->read_long(); + $ret[] = $args->read_long(); + return $ret; + } + + + + /** + * @return array + */ + public function queueBind($ticket = 1, $queue = '', $exchange, $routing_key = '', $nowait = false, $arguments = array()) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + $args->write_bits(array($nowait)); + $args->write_table(empty($arguments) ? array() : $arguments); + return array(50, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function queueBindOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function queuePurge($ticket = 1, $queue = '', $nowait = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_bits(array($nowait)); + return array(50, 30, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function queuePurgeOk($args) + { + $ret = array(); + $ret[] = $args->read_long(); + return $ret; + } + + + + /** + * @return array + */ + public function queueDelete($ticket = 1, $queue = '', $if_unused = false, $if_empty = false, $nowait = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_bits(array($if_unused, $if_empty, $nowait)); + return array(50, 40, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function queueDeleteOk($args) + { + $ret = array(); + $ret[] = $args->read_long(); + return $ret; + } + + + + /** + * @return array + */ + public function queueUnbind($ticket = 1, $queue = '', $exchange, $routing_key = '', $arguments = array()) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + $args->write_table(empty($arguments) ? array() : $arguments); + return array(50, 50, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function queueUnbindOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function basicQos($prefetch_size = 0, $prefetch_count = 0, $global = false) + { + $args = new AMQPWriter(); + $args->write_long($prefetch_size); + $args->write_short($prefetch_count); + $args->write_bits(array($global)); + return array(60, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function basicQosOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function basicConsume($ticket = 1, $queue = '', $consumer_tag = '', $no_local = false, $no_ack = false, $exclusive = false, $nowait = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_shortstr($consumer_tag); + $args->write_bits(array($no_local, $no_ack, $exclusive, $nowait)); + return array(60, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function basicConsumeOk($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function basicCancel($consumer_tag, $nowait = false) + { + $args = new AMQPWriter(); + $args->write_shortstr($consumer_tag); + $args->write_bits(array($nowait)); + return array(60, 30, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function basicCancelOk($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function basicPublish($ticket = 1, $exchange = '', $routing_key = '', $mandatory = false, $immediate = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + $args->write_bits(array($mandatory, $immediate)); + return array(60, 40, $args); + } + + + + /** + * @return array + */ + public function basicReturn($reply_code, $reply_text = '', $exchange, $routing_key) + { + $args = new AMQPWriter(); + $args->write_short($reply_code); + $args->write_shortstr($reply_text); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + return array(60, 50, $args); + } + + + + /** + * @return array + */ + public function basicDeliver($consumer_tag, $delivery_tag, $redelivered = false, $exchange, $routing_key) + { + $args = new AMQPWriter(); + $args->write_shortstr($consumer_tag); + $args->write_longlong($delivery_tag); + $args->write_bits(array($redelivered)); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + return array(60, 60, $args); + } + + + + /** + * @return array + */ + public function basicGet($ticket = 1, $queue = '', $no_ack = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_bits(array($no_ack)); + return array(60, 70, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function basicGetOk($args) + { + $ret = array(); + $ret[] = $args->read_longlong(); + $ret[] = $args->read_bit(); + $ret[] = $args->read_shortstr(); + $ret[] = $args->read_shortstr(); + $ret[] = $args->read_long(); + return $ret; + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function basicGetEmpty($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function basicAck($delivery_tag = 0, $multiple = false) + { + $args = new AMQPWriter(); + $args->write_longlong($delivery_tag); + $args->write_bits(array($multiple)); + return array(60, 80, $args); + } + + + + /** + * @return array + */ + public function basicReject($delivery_tag, $requeue = true) + { + $args = new AMQPWriter(); + $args->write_longlong($delivery_tag); + $args->write_bits(array($requeue)); + return array(60, 90, $args); + } + + + + /** + * @return array + */ + public function basicRecoverAsync($requeue = false) + { + $args = new AMQPWriter(); + $args->write_bits(array($requeue)); + return array(60, 100, $args); + } + + + + /** + * @return array + */ + public function basicRecover($requeue = false) + { + $args = new AMQPWriter(); + $args->write_bits(array($requeue)); + return array(60, 110, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function basicRecoverOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function fileQos($prefetch_size = 0, $prefetch_count = 0, $global = false) + { + $args = new AMQPWriter(); + $args->write_long($prefetch_size); + $args->write_short($prefetch_count); + $args->write_bits(array($global)); + return array(70, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function fileQosOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function fileConsume($ticket = 1, $queue = '', $consumer_tag = '', $no_local = false, $no_ack = false, $exclusive = false, $nowait = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_shortstr($consumer_tag); + $args->write_bits(array($no_local, $no_ack, $exclusive, $nowait)); + return array(70, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function fileConsumeOk($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function fileCancel($consumer_tag, $nowait = false) + { + $args = new AMQPWriter(); + $args->write_shortstr($consumer_tag); + $args->write_bits(array($nowait)); + return array(70, 30, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function fileCancelOk($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function fileOpen($identifier, $content_size) + { + $args = new AMQPWriter(); + $args->write_shortstr($identifier); + $args->write_longlong($content_size); + return array(70, 40, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function fileOpenOk($args) + { + $ret = array(); + $ret[] = $args->read_longlong(); + return $ret; + } + + + + /** + * @return array + */ + public function fileStage() + { + $args = new AMQPWriter(); + return array(70, 50, $args); + } + + + + /** + * @return array + */ + public function filePublish($ticket = 1, $exchange = '', $routing_key = '', $mandatory = false, $immediate = false, $identifier) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + $args->write_bits(array($mandatory, $immediate)); + $args->write_shortstr($identifier); + return array(70, 60, $args); + } + + + + /** + * @return array + */ + public function fileReturn($reply_code = 200, $reply_text = '', $exchange, $routing_key) + { + $args = new AMQPWriter(); + $args->write_short($reply_code); + $args->write_shortstr($reply_text); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + return array(70, 70, $args); + } + + + + /** + * @return array + */ + public function fileDeliver($consumer_tag, $delivery_tag, $redelivered = false, $exchange, $routing_key, $identifier) + { + $args = new AMQPWriter(); + $args->write_shortstr($consumer_tag); + $args->write_longlong($delivery_tag); + $args->write_bits(array($redelivered)); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + $args->write_shortstr($identifier); + return array(70, 80, $args); + } + + + + /** + * @return array + */ + public function fileAck($delivery_tag = 0, $multiple = false) + { + $args = new AMQPWriter(); + $args->write_longlong($delivery_tag); + $args->write_bits(array($multiple)); + return array(70, 90, $args); + } + + + + /** + * @return array + */ + public function fileReject($delivery_tag, $requeue = true) + { + $args = new AMQPWriter(); + $args->write_longlong($delivery_tag); + $args->write_bits(array($requeue)); + return array(70, 100, $args); + } + + + + /** + * @return array + */ + public function streamQos($prefetch_size = 0, $prefetch_count = 0, $consume_rate = 0, $global = false) + { + $args = new AMQPWriter(); + $args->write_long($prefetch_size); + $args->write_short($prefetch_count); + $args->write_long($consume_rate); + $args->write_bits(array($global)); + return array(80, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function streamQosOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function streamConsume($ticket = 1, $queue = '', $consumer_tag = '', $no_local = false, $exclusive = false, $nowait = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_shortstr($consumer_tag); + $args->write_bits(array($no_local, $exclusive, $nowait)); + return array(80, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function streamConsumeOk($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function streamCancel($consumer_tag, $nowait = false) + { + $args = new AMQPWriter(); + $args->write_shortstr($consumer_tag); + $args->write_bits(array($nowait)); + return array(80, 30, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function streamCancelOk($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function streamPublish($ticket = 1, $exchange = '', $routing_key = '', $mandatory = false, $immediate = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + $args->write_bits(array($mandatory, $immediate)); + return array(80, 40, $args); + } + + + + /** + * @return array + */ + public function streamReturn($reply_code = 200, $reply_text = '', $exchange, $routing_key) + { + $args = new AMQPWriter(); + $args->write_short($reply_code); + $args->write_shortstr($reply_text); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + return array(80, 50, $args); + } + + + + /** + * @return array + */ + public function streamDeliver($consumer_tag, $delivery_tag, $exchange, $queue) + { + $args = new AMQPWriter(); + $args->write_shortstr($consumer_tag); + $args->write_longlong($delivery_tag); + $args->write_shortstr($exchange); + $args->write_shortstr($queue); + return array(80, 60, $args); + } + + + + /** + * @return array + */ + public function txSelect() + { + $args = new AMQPWriter(); + return array(90, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function txSelectOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function txCommit() + { + $args = new AMQPWriter(); + return array(90, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function txCommitOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function txRollback() + { + $args = new AMQPWriter(); + return array(90, 30, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function txRollbackOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function dtxSelect() + { + $args = new AMQPWriter(); + return array(100, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function dtxSelectOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function dtxStart($dtx_identifier) + { + $args = new AMQPWriter(); + $args->write_shortstr($dtx_identifier); + return array(100, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function dtxStartOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function tunnelRequest($meta_data) + { + $args = new AMQPWriter(); + $args->write_table(empty($meta_data) ? array() : $meta_data); + return array(110, 10, $args); + } + + + + /** + * @return array + */ + public function testInteger($integer_1, $integer_2, $integer_3, $integer_4, $operation) + { + $args = new AMQPWriter(); + $args->write_octet($integer_1); + $args->write_short($integer_2); + $args->write_long($integer_3); + $args->write_longlong($integer_4); + $args->write_octet($operation); + return array(120, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function testIntegerOk($args) + { + $ret = array(); + $ret[] = $args->read_longlong(); + return $ret; + } + + + + /** + * @return array + */ + public function testString($string_1, $string_2, $operation) + { + $args = new AMQPWriter(); + $args->write_shortstr($string_1); + $args->write_longstr($string_2); + $args->write_octet($operation); + return array(120, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function testStringOk($args) + { + $ret = array(); + $ret[] = $args->read_longstr(); + return $ret; + } + + + + /** + * @return array + */ + public function testTable($table, $integer_op, $string_op) + { + $args = new AMQPWriter(); + $args->write_table(empty($table) ? array() : $table); + $args->write_octet($integer_op); + $args->write_octet($string_op); + return array(120, 30, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function testTableOk($args) + { + $ret = array(); + $ret[] = $args->read_longlong(); + $ret[] = $args->read_longstr(); + return $ret; + } + + + + /** + * @return array + */ + public function testContent() + { + $args = new AMQPWriter(); + return array(120, 40, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function testContentOk($args) + { + $ret = array(); + $ret[] = $args->read_long(); + return $ret; + } + +} diff --git a/lib/PhpAmqpLib/Helper/Protocol/Protocol091.php b/lib/PhpAmqpLib/Helper/Protocol/Protocol091.php new file mode 100644 index 0000000000..fcca7333da --- /dev/null +++ b/lib/PhpAmqpLib/Helper/Protocol/Protocol091.php @@ -0,0 +1,870 @@ +write_octet($version_major); + $args->write_octet($version_minor); + $args->write_table(empty($server_properties) ? array() : $server_properties); + $args->write_longstr($mechanisms); + $args->write_longstr($locales); + return array(10, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function connectionStartOk($args) + { + $ret = array(); + $ret[] = $args->read_table(); + $ret[] = $args->read_shortstr(); + $ret[] = $args->read_longstr(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function connectionSecure($challenge) + { + $args = new AMQPWriter(); + $args->write_longstr($challenge); + return array(10, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function connectionSecureOk($args) + { + $ret = array(); + $ret[] = $args->read_longstr(); + return $ret; + } + + + + /** + * @return array + */ + public function connectionTune($channel_max = 0, $frame_max = 0, $heartbeat = 0) + { + $args = new AMQPWriter(); + $args->write_short($channel_max); + $args->write_long($frame_max); + $args->write_short($heartbeat); + return array(10, 30, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function connectionTuneOk($args) + { + $ret = array(); + $ret[] = $args->read_short(); + $ret[] = $args->read_long(); + $ret[] = $args->read_short(); + return $ret; + } + + + + /** + * @return array + */ + public function connectionOpen($virtual_host = '/', $capabilities = '', $insist = false) + { + $args = new AMQPWriter(); + $args->write_shortstr($virtual_host); + $args->write_shortstr($capabilities); + $args->write_bits(array($insist)); + return array(10, 40, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function connectionOpenOk($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function connectionClose($reply_code, $reply_text = '', $class_id, $method_id) + { + $args = new AMQPWriter(); + $args->write_short($reply_code); + $args->write_shortstr($reply_text); + $args->write_short($class_id); + $args->write_short($method_id); + return array(10, 50, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function connectionCloseOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function connectionBlocked($reason = '') + { + $args = new AMQPWriter(); + $args->write_shortstr($reason); + return array(10, 60, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function connectionUnblocked($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function channelOpen($out_of_band = '') + { + $args = new AMQPWriter(); + $args->write_shortstr($out_of_band); + return array(20, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function channelOpenOk($args) + { + $ret = array(); + $ret[] = $args->read_longstr(); + return $ret; + } + + + + /** + * @return array + */ + public function channelFlow($active) + { + $args = new AMQPWriter(); + $args->write_bits(array($active)); + return array(20, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function channelFlowOk($args) + { + $ret = array(); + $ret[] = $args->read_bit(); + return $ret; + } + + + + /** + * @return array + */ + public function channelClose($reply_code, $reply_text = '', $class_id, $method_id) + { + $args = new AMQPWriter(); + $args->write_short($reply_code); + $args->write_shortstr($reply_text); + $args->write_short($class_id); + $args->write_short($method_id); + return array(20, 40, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function channelCloseOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function accessRequest($realm = '/data', $exclusive = false, $passive = true, $active = true, $write = true, $read = true) + { + $args = new AMQPWriter(); + $args->write_shortstr($realm); + $args->write_bits(array($exclusive, $passive, $active, $write, $read)); + return array(30, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function accessRequestOk($args) + { + $ret = array(); + $ret[] = $args->read_short(); + return $ret; + } + + + + /** + * @return array + */ + public function exchangeDeclare($ticket = 0, $exchange, $type = 'direct', $passive = false, $durable = false, $auto_delete = false, $internal = false, $nowait = false, $arguments = array()) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($exchange); + $args->write_shortstr($type); + $args->write_bits(array($passive, $durable, $auto_delete, $internal, $nowait)); + $args->write_table(empty($arguments) ? array() : $arguments); + return array(40, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function exchangeDeclareOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function exchangeDelete($ticket = 0, $exchange, $if_unused = false, $nowait = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($exchange); + $args->write_bits(array($if_unused, $nowait)); + return array(40, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function exchangeDeleteOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function exchangeBind($ticket = 0, $destination, $source, $routing_key = '', $nowait = false, $arguments = array()) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($destination); + $args->write_shortstr($source); + $args->write_shortstr($routing_key); + $args->write_bits(array($nowait)); + $args->write_table(empty($arguments) ? array() : $arguments); + return array(40, 30, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function exchangeBindOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function exchangeUnbind($ticket = 0, $destination, $source, $routing_key = '', $nowait = false, $arguments = array()) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($destination); + $args->write_shortstr($source); + $args->write_shortstr($routing_key); + $args->write_bits(array($nowait)); + $args->write_table(empty($arguments) ? array() : $arguments); + return array(40, 40, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function exchangeUnbindOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function queueDeclare($ticket = 0, $queue = '', $passive = false, $durable = false, $exclusive = false, $auto_delete = false, $nowait = false, $arguments = array()) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_bits(array($passive, $durable, $exclusive, $auto_delete, $nowait)); + $args->write_table(empty($arguments) ? array() : $arguments); + return array(50, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function queueDeclareOk($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + $ret[] = $args->read_long(); + $ret[] = $args->read_long(); + return $ret; + } + + + + /** + * @return array + */ + public function queueBind($ticket = 0, $queue = '', $exchange, $routing_key = '', $nowait = false, $arguments = array()) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + $args->write_bits(array($nowait)); + $args->write_table(empty($arguments) ? array() : $arguments); + return array(50, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function queueBindOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function queuePurge($ticket = 0, $queue = '', $nowait = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_bits(array($nowait)); + return array(50, 30, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function queuePurgeOk($args) + { + $ret = array(); + $ret[] = $args->read_long(); + return $ret; + } + + + + /** + * @return array + */ + public function queueDelete($ticket = 0, $queue = '', $if_unused = false, $if_empty = false, $nowait = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_bits(array($if_unused, $if_empty, $nowait)); + return array(50, 40, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function queueDeleteOk($args) + { + $ret = array(); + $ret[] = $args->read_long(); + return $ret; + } + + + + /** + * @return array + */ + public function queueUnbind($ticket = 0, $queue = '', $exchange, $routing_key = '', $arguments = array()) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + $args->write_table(empty($arguments) ? array() : $arguments); + return array(50, 50, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function queueUnbindOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function basicQos($prefetch_size = 0, $prefetch_count = 0, $global = false) + { + $args = new AMQPWriter(); + $args->write_long($prefetch_size); + $args->write_short($prefetch_count); + $args->write_bits(array($global)); + return array(60, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function basicQosOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function basicConsume($ticket = 0, $queue = '', $consumer_tag = '', $no_local = false, $no_ack = false, $exclusive = false, $nowait = false, $arguments = array()) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_shortstr($consumer_tag); + $args->write_bits(array($no_local, $no_ack, $exclusive, $nowait)); + $args->write_table(empty($arguments) ? array() : $arguments); + return array(60, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function basicConsumeOk($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function basicCancel($consumer_tag, $nowait = false) + { + $args = new AMQPWriter(); + $args->write_shortstr($consumer_tag); + $args->write_bits(array($nowait)); + return array(60, 30, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function basicCancelOk($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function basicPublish($ticket = 0, $exchange = '', $routing_key = '', $mandatory = false, $immediate = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + $args->write_bits(array($mandatory, $immediate)); + return array(60, 40, $args); + } + + + + /** + * @return array + */ + public function basicReturn($reply_code, $reply_text = '', $exchange, $routing_key) + { + $args = new AMQPWriter(); + $args->write_short($reply_code); + $args->write_shortstr($reply_text); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + return array(60, 50, $args); + } + + + + /** + * @return array + */ + public function basicDeliver($consumer_tag, $delivery_tag, $redelivered = false, $exchange, $routing_key) + { + $args = new AMQPWriter(); + $args->write_shortstr($consumer_tag); + $args->write_longlong($delivery_tag); + $args->write_bits(array($redelivered)); + $args->write_shortstr($exchange); + $args->write_shortstr($routing_key); + return array(60, 60, $args); + } + + + + /** + * @return array + */ + public function basicGet($ticket = 0, $queue = '', $no_ack = false) + { + $args = new AMQPWriter(); + $args->write_short($ticket); + $args->write_shortstr($queue); + $args->write_bits(array($no_ack)); + return array(60, 70, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function basicGetOk($args) + { + $ret = array(); + $ret[] = $args->read_longlong(); + $ret[] = $args->read_bit(); + $ret[] = $args->read_shortstr(); + $ret[] = $args->read_shortstr(); + $ret[] = $args->read_long(); + return $ret; + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function basicGetEmpty($args) + { + $ret = array(); + $ret[] = $args->read_shortstr(); + return $ret; + } + + + + /** + * @return array + */ + public function basicAck($delivery_tag = 0, $multiple = false) + { + $args = new AMQPWriter(); + $args->write_longlong($delivery_tag); + $args->write_bits(array($multiple)); + return array(60, 80, $args); + } + + + + /** + * @return array + */ + public function basicReject($delivery_tag, $requeue = true) + { + $args = new AMQPWriter(); + $args->write_longlong($delivery_tag); + $args->write_bits(array($requeue)); + return array(60, 90, $args); + } + + + + /** + * @return array + */ + public function basicRecoverAsync($requeue = false) + { + $args = new AMQPWriter(); + $args->write_bits(array($requeue)); + return array(60, 100, $args); + } + + + + /** + * @return array + */ + public function basicRecover($requeue = false) + { + $args = new AMQPWriter(); + $args->write_bits(array($requeue)); + return array(60, 110, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function basicRecoverOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function basicNack($delivery_tag = 0, $multiple = false, $requeue = true) + { + $args = new AMQPWriter(); + $args->write_longlong($delivery_tag); + $args->write_bits(array($multiple, $requeue)); + return array(60, 120, $args); + } + + + + /** + * @return array + */ + public function txSelect() + { + $args = new AMQPWriter(); + return array(90, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function txSelectOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function txCommit() + { + $args = new AMQPWriter(); + return array(90, 20, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function txCommitOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function txRollback() + { + $args = new AMQPWriter(); + return array(90, 30, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function txRollbackOk($args) + { + $ret = array(); + return $ret; + } + + + + /** + * @return array + */ + public function confirmSelect($nowait = false) + { + $args = new AMQPWriter(); + $args->write_bits(array($nowait)); + return array(85, 10, $args); + } + + + + /** + * @param AMQPReader $args + * @return array + */ + public static function confirmSelectOk($args) + { + $ret = array(); + return $ret; + } + +} diff --git a/lib/PhpAmqpLib/Helper/Protocol/Wait080.php b/lib/PhpAmqpLib/Helper/Protocol/Wait080.php new file mode 100644 index 0000000000..93badd2fbb --- /dev/null +++ b/lib/PhpAmqpLib/Helper/Protocol/Wait080.php @@ -0,0 +1,113 @@ + '10,10', + 'connection.start_ok' => '10,11', + 'connection.secure' => '10,20', + 'connection.secure_ok' => '10,21', + 'connection.tune' => '10,30', + 'connection.tune_ok' => '10,31', + 'connection.open' => '10,40', + 'connection.open_ok' => '10,41', + 'connection.redirect' => '10,50', + 'connection.close' => '10,60', + 'connection.close_ok' => '10,61', + 'channel.open' => '20,10', + 'channel.open_ok' => '20,11', + 'channel.flow' => '20,20', + 'channel.flow_ok' => '20,21', + 'channel.alert' => '20,30', + 'channel.close' => '20,40', + 'channel.close_ok' => '20,41', + 'access.request' => '30,10', + 'access.request_ok' => '30,11', + 'exchange.declare' => '40,10', + 'exchange.declare_ok' => '40,11', + 'exchange.delete' => '40,20', + 'exchange.delete_ok' => '40,21', + 'queue.declare' => '50,10', + 'queue.declare_ok' => '50,11', + 'queue.bind' => '50,20', + 'queue.bind_ok' => '50,21', + 'queue.purge' => '50,30', + 'queue.purge_ok' => '50,31', + 'queue.delete' => '50,40', + 'queue.delete_ok' => '50,41', + 'queue.unbind' => '50,50', + 'queue.unbind_ok' => '50,51', + 'basic.qos' => '60,10', + 'basic.qos_ok' => '60,11', + 'basic.consume' => '60,20', + 'basic.consume_ok' => '60,21', + 'basic.cancel' => '60,30', + 'basic.cancel_ok' => '60,31', + 'basic.publish' => '60,40', + 'basic.return' => '60,50', + 'basic.deliver' => '60,60', + 'basic.get' => '60,70', + 'basic.get_ok' => '60,71', + 'basic.get_empty' => '60,72', + 'basic.ack' => '60,80', + 'basic.reject' => '60,90', + 'basic.recover_async' => '60,100', + 'basic.recover' => '60,110', + 'basic.recover_ok' => '60,111', + 'file.qos' => '70,10', + 'file.qos_ok' => '70,11', + 'file.consume' => '70,20', + 'file.consume_ok' => '70,21', + 'file.cancel' => '70,30', + 'file.cancel_ok' => '70,31', + 'file.open' => '70,40', + 'file.open_ok' => '70,41', + 'file.stage' => '70,50', + 'file.publish' => '70,60', + 'file.return' => '70,70', + 'file.deliver' => '70,80', + 'file.ack' => '70,90', + 'file.reject' => '70,100', + 'stream.qos' => '80,10', + 'stream.qos_ok' => '80,11', + 'stream.consume' => '80,20', + 'stream.consume_ok' => '80,21', + 'stream.cancel' => '80,30', + 'stream.cancel_ok' => '80,31', + 'stream.publish' => '80,40', + 'stream.return' => '80,50', + 'stream.deliver' => '80,60', + 'tx.select' => '90,10', + 'tx.select_ok' => '90,11', + 'tx.commit' => '90,20', + 'tx.commit_ok' => '90,21', + 'tx.rollback' => '90,30', + 'tx.rollback_ok' => '90,31', + 'dtx.select' => '100,10', + 'dtx.select_ok' => '100,11', + 'dtx.start' => '100,20', + 'dtx.start_ok' => '100,21', + 'tunnel.request' => '110,10', + 'test.integer' => '120,10', + 'test.integer_ok' => '120,11', + 'test.string' => '120,20', + 'test.string_ok' => '120,21', + 'test.table' => '120,30', + 'test.table_ok' => '120,31', + 'test.content' => '120,40', + 'test.content_ok' => '120,41', + ); + + + + public function get_wait($method) + { + return $this->wait[$method]; + } + +} diff --git a/lib/PhpAmqpLib/Helper/Protocol/Wait091.php b/lib/PhpAmqpLib/Helper/Protocol/Wait091.php new file mode 100644 index 0000000000..9533db9fae --- /dev/null +++ b/lib/PhpAmqpLib/Helper/Protocol/Wait091.php @@ -0,0 +1,84 @@ + '10,10', + 'connection.start_ok' => '10,11', + 'connection.secure' => '10,20', + 'connection.secure_ok' => '10,21', + 'connection.tune' => '10,30', + 'connection.tune_ok' => '10,31', + 'connection.open' => '10,40', + 'connection.open_ok' => '10,41', + 'connection.close' => '10,50', + 'connection.close_ok' => '10,51', + 'connection.blocked' => '10,60', + 'connection.unblocked' => '10,61', + 'channel.open' => '20,10', + 'channel.open_ok' => '20,11', + 'channel.flow' => '20,20', + 'channel.flow_ok' => '20,21', + 'channel.close' => '20,40', + 'channel.close_ok' => '20,41', + 'access.request' => '30,10', + 'access.request_ok' => '30,11', + 'exchange.declare' => '40,10', + 'exchange.declare_ok' => '40,11', + 'exchange.delete' => '40,20', + 'exchange.delete_ok' => '40,21', + 'exchange.bind' => '40,30', + 'exchange.bind_ok' => '40,31', + 'exchange.unbind' => '40,40', + 'exchange.unbind_ok' => '40,51', + 'queue.declare' => '50,10', + 'queue.declare_ok' => '50,11', + 'queue.bind' => '50,20', + 'queue.bind_ok' => '50,21', + 'queue.purge' => '50,30', + 'queue.purge_ok' => '50,31', + 'queue.delete' => '50,40', + 'queue.delete_ok' => '50,41', + 'queue.unbind' => '50,50', + 'queue.unbind_ok' => '50,51', + 'basic.qos' => '60,10', + 'basic.qos_ok' => '60,11', + 'basic.consume' => '60,20', + 'basic.consume_ok' => '60,21', + 'basic.cancel' => '60,30', + 'basic.cancel_ok' => '60,31', + 'basic.publish' => '60,40', + 'basic.return' => '60,50', + 'basic.deliver' => '60,60', + 'basic.get' => '60,70', + 'basic.get_ok' => '60,71', + 'basic.get_empty' => '60,72', + 'basic.ack' => '60,80', + 'basic.reject' => '60,90', + 'basic.recover_async' => '60,100', + 'basic.recover' => '60,110', + 'basic.recover_ok' => '60,111', + 'basic.nack' => '60,120', + 'tx.select' => '90,10', + 'tx.select_ok' => '90,11', + 'tx.commit' => '90,20', + 'tx.commit_ok' => '90,21', + 'tx.rollback' => '90,30', + 'tx.rollback_ok' => '90,31', + 'confirm.select' => '85,10', + 'confirm.select_ok' => '85,11', + ); + + + + public function get_wait($method) + { + return $this->wait[$method]; + } + +} diff --git a/lib/PhpAmqpLib/Message/AMQPMessage.php b/lib/PhpAmqpLib/Message/AMQPMessage.php new file mode 100644 index 0000000000..fca9b19204 --- /dev/null +++ b/lib/PhpAmqpLib/Message/AMQPMessage.php @@ -0,0 +1,57 @@ + 'shortstr', + 'content_encoding' => 'shortstr', + 'application_headers' => 'table_object', + 'delivery_mode' => 'octet', + 'priority' => 'octet', + 'correlation_id' => 'shortstr', + 'reply_to' => 'shortstr', + 'expiration' => 'shortstr', + 'message_id' => 'shortstr', + 'timestamp' => 'timestamp', + 'type' => 'shortstr', + 'user_id' => 'shortstr', + 'app_id' => 'shortstr', + 'cluster_id' => 'shortstr', + ); + + /** + * @param string $body + * @param null $properties + */ + public function __construct($body = '', $properties = null) + { + $this->setBody($body); + parent::__construct($properties, static::$propertyDefinitions); + } + + /** + * Sets the message payload + * + * @param mixed $body + * @return $this + */ + public function setBody($body) + { + $this->body = $body; + } +} diff --git a/lib/PhpAmqpLib/Wire/AMQPAbstractCollection.php b/lib/PhpAmqpLib/Wire/AMQPAbstractCollection.php new file mode 100644 index 0000000000..7789da5e45 --- /dev/null +++ b/lib/PhpAmqpLib/Wire/AMQPAbstractCollection.php @@ -0,0 +1,446 @@ + 'I', + self::T_DECIMAL => 'D', + self::T_TIMESTAMP => 'T', + self::T_STRING_LONG => 'S', + self::T_TABLE => 'F' + ); + + /** + * @var array + */ + private static $_types_091 = array( + self::T_INT_SHORTSHORT => 'b', + self::T_INT_SHORTSHORT_U => 'B', + self::T_INT_SHORT => 'U', + self::T_INT_SHORT_U => 'u', + self::T_INT_LONG => 'I', + self::T_INT_LONG_U => 'i', + self::T_INT_LONGLONG => 'L', + self::T_INT_LONGLONG_U => 'l', + self::T_DECIMAL => 'D', + self::T_TIMESTAMP => 'T', + self::T_VOID => 'V', + self::T_BOOL => 't', + self::T_STRING_SHORT => 's', + self::T_STRING_LONG => 'S', + self::T_ARRAY => 'A', + self::T_TABLE => 'F' + ); + + /** + * @var array + */ + private static $_types_rabbit = array( + self::T_INT_SHORTSHORT => 'b', + self::T_INT_SHORT => 's', + self::T_INT_LONG => 'I', + self::T_INT_LONGLONG => 'l', + self::T_DECIMAL => 'D', + self::T_TIMESTAMP => 'T', + self::T_VOID => 'V', + self::T_BOOL => 't', + self::T_STRING_LONG => 'S', + self::T_ARRAY => 'A', + self::T_TABLE => 'F' + ); + + /** + * @var array + */ + protected $data = array(); + + public function __construct(array $data = null) + { + if (!empty($data)) { + $this->data = $this->encodeCollection($data); + } + } + + /** + * @return int + */ + abstract public function getType(); + + /** + * @param mixed $val + * @param int $type + * @param string $key + */ + final protected function setValue($val, $type = null, $key = null) + { + if ($val instanceof self) { + if ($type && ($type != $val->getType())) { + throw new Exception\AMQPInvalidArgumentException( + 'Attempted to add instance of ' . get_class($val) . ' representing type [' . $val->getType() . '] as mismatching type [' . $type . ']' + ); + } + $type = $val->getType(); + } elseif ($type) { //ensuring data integrity and that all members are properly validated + switch ($type) { + case self::T_ARRAY: + throw new Exception\AMQPInvalidArgumentException('Arrays must be passed as AMQPArray instance'); + break; + case self::T_TABLE: + throw new Exception\AMQPInvalidArgumentException('Tables must be passed as AMQPTable instance'); + break; + case self::T_DECIMAL: + if (!($val instanceof AMQPDecimal)) { + throw new Exception\AMQPInvalidArgumentException('Decimal values must be instance of AMQPDecimal'); + } + break; + } + } + + if ($type) { + self::checkDataTypeIsSupported($type, false); + $val = array($type, $val); + } else { + $val = $this->encodeValue($val); + } + + if ($key === null) { + $this->data[] = $val; + } else { + $this->data[$key] = $val; + } + } + + /** + * @return array + */ + final public function getNativeData() + { + return $this->decodeCollection($this->data); + } + + /** + * @param array $val + * @return array + */ + final protected function encodeCollection(array $val) + { + foreach ($val as &$v) { + $v = $this->encodeValue($v); + } + unset($v); + + return $val; + } + + /** + * @param array $val + * @return array + */ + final protected function decodeCollection(array $val) + { + foreach ($val as &$v) { + $v = $this->decodeValue($v[1], $v[0]); + } + unset($v); + + return $val; + } + + /** + * @param mixed $val + * @return mixed + * @throws Exception\AMQPOutOfBoundsException + */ + protected function encodeValue($val) + { + if (is_string($val)) { + $val = $this->encodeString($val); + } elseif (is_float($val)) { + $val = $this->encodeFloat($val); + } elseif (is_int($val)) { + $val = $this->encodeInt($val); + } elseif (is_bool($val)) { + $val = $this->encodeBool($val); + } elseif (is_null($val)) { + $val = $this->encodeVoid(); + } elseif ($val instanceof \DateTime) { + $val = array(self::T_TIMESTAMP, $val->getTimestamp()); + } elseif ($val instanceof AMQPDecimal) { + $val = array(self::T_DECIMAL, $val); + } elseif ($val instanceof self) { + //avoid silent type correction of strictly typed values + self::checkDataTypeIsSupported($val->getType(), false); + $val = array($val->getType(), $val); + } elseif (is_array($val)) { + //AMQP specs says "Field names MUST start with a letter, '$' or '#'" + //so beware, some servers may raise an exception with 503 code in cases when indexed array is encoded as table + if (self::isProtocol(self::PROTOCOL_080)) { + //080 doesn't support arrays, forcing table + $val = array(self::T_TABLE, new AMQPTable($val)); + } elseif (empty($val) || (array_keys($val) === range(0, count($val) - 1))) { + $val = array(self::T_ARRAY, new AMQPArray($val)); + } else { + $val = array(self::T_TABLE, new AMQPTable($val)); + } + } else { + throw new Exception\AMQPOutOfBoundsException(sprintf('Encountered value of unsupported type: %s', gettype($val))); + } + + return $val; + } + + /** + * @param mixed $val + * @param integer $type + * @return array|bool|\DateTime|null + */ + protected function decodeValue($val, $type) + { + if ($val instanceof self) { + //covering arrays and tables + $val = $val->getNativeData(); + } else { + switch ($type) { + case self::T_BOOL: + $val = (bool) $val; + break; + case self::T_TIMESTAMP: + $val = \DateTime::createFromFormat('U', $val); + break; + case self::T_VOID: + $val = null; + break; + case self::T_ARRAY: + case self::T_TABLE: + throw new Exception\AMQPLogicException( + 'Encountered an array/table struct which is not an instance of AMQPCollection. ' . + 'This is considered a bug and should be fixed, please report' + ); + } + } + + return $val; + } + + /** + * @param string $val + * @return array + */ + protected function encodeString($val) + { + return array(self::T_STRING_LONG, $val); + } + + /** + * @param int $val + * @return array + */ + protected function encodeInt($val) + { + if (($val >= -2147483648) && ($val <= 2147483647)) { + $ev = array(self::T_INT_LONG, $val); + } elseif (self::isProtocol(self::PROTOCOL_080)) { + //080 doesn't support longlong + $ev = $this->encodeString((string) $val); + } else { + $ev = array(self::T_INT_LONGLONG, $val); + } + + return $ev; + } + + /** + * @param float $val + * @return array + */ + protected function encodeFloat($val) + { + return static::encodeString((string) $val); + } + + /** + * @param bool $val + * @return array + */ + protected function encodeBool($val) + { + $val = (bool) $val; + + return self::isProtocol(self::PROTOCOL_080) ? array(self::T_INT_LONG, (int) $val) : array(self::T_BOOL, $val); + } + + /** + * @return array + */ + protected function encodeVoid() + { + return self::isProtocol(self::PROTOCOL_080) ? $this->encodeString('') : array(self::T_VOID, null); + } + + /** + * @return string + */ + final public static function getProtocol() + { + if (self::$_protocol === null) { + self::$_protocol = defined('AMQP_STRICT_FLD_TYPES') && AMQP_STRICT_FLD_TYPES ? + AbstractChannel::getProtocolVersion() : + self::PROTOCOL_RBT; + } + + return self::$_protocol; + } + + /** + * @param string $proto + * @return bool + */ + final public static function isProtocol($proto) + { + return self::getProtocol() == $proto; + } + + /** + * @return array [dataTypeConstant => dataTypeSymbol] + */ + final public static function getSupportedDataTypes() + { + switch ($proto = self::getProtocol()) { + case self::PROTOCOL_080: + $types = self::$_types_080; + break; + case self::PROTOCOL_091: + $types = self::$_types_091; + break; + case self::PROTOCOL_RBT: + $types = self::$_types_rabbit; + break; + default: + throw new Exception\AMQPOutOfRangeException(sprintf('Unknown protocol: %s', $proto)); + } + + return $types; + } + + /** + * @param string $type + * @param bool $return Whether to return or raise AMQPOutOfRangeException + * @return boolean + */ + final public static function checkDataTypeIsSupported($type, $return = true) + { + try { + $supported = self::getSupportedDataTypes(); + if (!isset($supported[$type])) { + throw new Exception\AMQPOutOfRangeException(sprintf('AMQP-%s doesn\'t support data of type [%s]', self::getProtocol(), $type)); + } + return true; + + } catch (Exception\AMQPOutOfRangeException $ex) { + if (!$return) { + throw $ex; + } + + return false; + } + } + + /** + * @param integer $type + * @return string + */ + final public static function getSymbolForDataType($type) + { + $types = self::getSupportedDataTypes(); + if (!isset($types[$type])) { + throw new Exception\AMQPOutOfRangeException(sprintf('AMQP-%s doesn\'t support data of type [%s]', self::getProtocol(), $type)); + } + + return $types[$type]; + } + + /** + * @param string $symbol + * @return integer + */ + final public static function getDataTypeForSymbol($symbol) + { + $symbols = array_flip(self::getSupportedDataTypes()); + if (!isset($symbols[$symbol])) { + throw new Exception\AMQPOutOfRangeException(sprintf('AMQP-%s doesn\'t define data of type [%s]', self::getProtocol(), $symbol)); + } + + return $symbols[$symbol]; + } + + public function current() + { + return current($this->data); + } + + public function key() + { + return key($this->data); + } + + public function next() + { + next($this->data); + } + + public function rewind() + { + reset($this->data); + } + + public function valid() + { + return key($this->data) !== null; + } +} diff --git a/lib/PhpAmqpLib/Wire/AMQPArray.php b/lib/PhpAmqpLib/Wire/AMQPArray.php new file mode 100644 index 0000000000..742925691b --- /dev/null +++ b/lib/PhpAmqpLib/Wire/AMQPArray.php @@ -0,0 +1,32 @@ +setValue($val, $type); + + return $this; + } +} diff --git a/lib/PhpAmqpLib/Wire/AMQPDecimal.php b/lib/PhpAmqpLib/Wire/AMQPDecimal.php new file mode 100644 index 0000000000..9768a4899f --- /dev/null +++ b/lib/PhpAmqpLib/Wire/AMQPDecimal.php @@ -0,0 +1,47 @@ +n = $n; + $this->e = $e; + } + + /** + * @return string + */ + public function asBCvalue() + { + return bcdiv($this->n, bcpow(10, $this->e)); + } +} diff --git a/lib/PhpAmqpLib/Wire/AMQPReader.php b/lib/PhpAmqpLib/Wire/AMQPReader.php new file mode 100644 index 0000000000..be1b76f221 --- /dev/null +++ b/lib/PhpAmqpLib/Wire/AMQPReader.php @@ -0,0 +1,534 @@ +str = $str; + $this->str_length = mb_strlen($this->str, 'ASCII'); + $this->io = $io; + $this->offset = 0; + $this->bitcount = $this->bits = 0; + $this->timeout = $timeout; + } + + /** + * Resets the object from the injected param + * + * Used to not need to create a new AMQPReader instance every time. + * when we can just pass a string and reset the object state. + * NOTE: since we are working with strings we don't need to pass an AbstractIO + * or a timeout. + * + * @param string $str + */ + public function reuse($str) + { + $this->str = $str; + $this->str_length = mb_strlen($this->str, 'ASCII'); + $this->offset = 0; + $this->bitcount = $this->bits = 0; + } + + /** + * Closes the stream + */ + public function close() + { + if ($this->io) { + $this->io->close(); + } + } + + /** + * @param $n + * @return string + */ + public function read($n) + { + $this->bitcount = $this->bits = 0; + + return $this->rawread($n); + } + + /** + * Waits until some data is retrieved from the socket. + * + * AMQPTimeoutException can be raised if the timeout is set + * + * @throws \PhpAmqpLib\Exception\AMQPTimeoutException + */ + protected function wait() + { + if ($this->timeout == 0) { + return; + } + + // wait .. + list($sec, $usec) = MiscHelper::splitSecondsMicroseconds($this->timeout); + $result = $this->io->select($sec, $usec); + + if ($result === false) { + throw new AMQPRuntimeException('A network error occured while awaiting for incoming data'); + } + + if ($result === 0) { + throw new AMQPTimeoutException(sprintf( + 'The connection timed out after %s sec while awaiting incoming data', + $this->getTimeout() + )); + } + } + + /** + * @param $n + * @return string + * @throws \RuntimeException + * @throws \PhpAmqpLib\Exception\AMQPRuntimeException + */ + protected function rawread($n) + { + if ($this->io) { + $this->wait(); + $res = $this->io->read($n); + $this->offset += $n; + } else { + if ($this->str_length < $n) { + throw new AMQPRuntimeException(sprintf( + 'Error reading data. Requested %s bytes while string buffer has only %s', + $n, + $this->str_length + )); + } + + $res = mb_substr($this->str, 0, $n, 'ASCII'); + $this->str = mb_substr($this->str, $n, mb_strlen($this->str, 'ASCII') - $n, 'ASCII'); + $this->str_length -= $n; + $this->offset += $n; + } + + return $res; + } + + /** + * @return bool + */ + public function read_bit() + { + if (!$this->bitcount) { + $this->bits = ord($this->rawread(1)); + $this->bitcount = 8; + } + + $result = ($this->bits & 1) == 1; + $this->bits >>= 1; + $this->bitcount -= 1; + + return $result; + } + + /** + * @return mixed + */ + public function read_octet() + { + $this->bitcount = $this->bits = 0; + list(, $res) = unpack('C', $this->rawread(1)); + + return $res; + } + + /** + * @return mixed + */ + public function read_signed_octet() + { + $this->bitcount = $this->bits = 0; + list(, $res) = unpack('c', $this->rawread(1)); + + return $res; + } + + /** + * @return mixed + */ + public function read_short() + { + $this->bitcount = $this->bits = 0; + list(, $res) = unpack('n', $this->rawread(2)); + + return $res; + } + + /** + * @return mixed + */ + public function read_signed_short() + { + $this->bitcount = $this->bits = 0; + list(, $res) = unpack('s', $this->correctEndianness($this->rawread(2))); + + return $res; + } + + /** + * Reads 32 bit integer in big-endian byte order. + * + * On 64 bit systems it will return always unsigned int + * value in 0..2^32 range. + * + * On 32 bit systems it will return signed int value in + * -2^31...+2^31 range. + * + * Use with caution! + */ + public function read_php_int() + { + list(, $res) = unpack('N', $this->rawread(4)); + if ($this->is64bits) { + $sres = sprintf('%u', $res); + return (int) $sres; + } else { + return $res; + } + } + + /** + * PHP does not have unsigned 32 bit int, + * so we return it as a string + * + * @return string + */ + public function read_long() + { + $this->bitcount = $this->bits = 0; + list(, $res) = unpack('N', $this->rawread(4)); + + return !$this->is64bits && self::getLongMSB($res) ? sprintf('%u', $res) : $res; + } + + /** + * @return integer + */ + private function read_signed_long() + { + $this->bitcount = $this->bits = 0; + list(, $res) = unpack('l', $this->correctEndianness($this->rawread(4))); + + return $res; + } + + /** + * Even on 64 bit systems PHP integers are singed. + * Since we need an unsigned value here we return it + * as a string. + * + * @return string + */ + public function read_longlong() + { + $this->bitcount = $this->bits = 0; + + list(, $hi, $lo) = unpack('N2', $this->rawread(8)); + $msb = self::getLongMSB($hi); + + if (!$this->is64bits) { + if ($msb) { + $hi = sprintf('%u', $hi); + } + if (self::getLongMSB($lo)) { + $lo = sprintf('%u', $lo); + } + } + + return bcadd($this->is64bits && !$msb ? $hi << 32 : bcmul($hi, '4294967296', 0), $lo, 0); + } + + /** + * @return string + */ + public function read_signed_longlong() + { + $this->bitcount = $this->bits = 0; + + list(, $hi, $lo) = unpack('N2', $this->rawread(8)); + + if ($this->is64bits) { + return bcadd($hi << 32, $lo, 0); + } else { + return bcadd(bcmul($hi, '4294967296', 0), self::getLongMSB($lo) ? sprintf('%u', $lo) : $lo, 0); + } + } + + /** + * @param int $longInt + * @return bool + */ + private static function getLongMSB($longInt) + { + return (bool) ($longInt & 0x80000000); + } + + /** + * Read a utf-8 encoded string that's stored in up to + * 255 bytes. Return it decoded as a PHP unicode object. + */ + public function read_shortstr() + { + $this->bitcount = $this->bits = 0; + list(, $slen) = unpack('C', $this->rawread(1)); + + return $this->rawread($slen); + } + + /** + * Read a string that's up to 2**32 bytes, the encoding + * isn't specified in the AMQP spec, so just return it as + * a plain PHP string. + */ + public function read_longstr() + { + $this->bitcount = $this->bits = 0; + $slen = $this->read_php_int(); + + if ($slen < 0) { + throw new AMQPOutOfBoundsException('Strings longer than supported on this platform'); + } + + return $this->rawread($slen); + } + + /** + * Read and AMQP timestamp, which is a 64-bit integer representing + * seconds since the Unix epoch in 1-second resolution. + */ + public function read_timestamp() + { + return $this->read_longlong(); + } + + /** + * Read an AMQP table, and return as a PHP array. keys are strings, + * values are (type,value) tuples. + * + * @param bool $returnObject Whether to return AMQPArray instance instead of plain array + * @return array|AMQPTable + */ + public function read_table($returnObject = false) + { + $this->bitcount = $this->bits = 0; + $tlen = $this->read_php_int(); + + if ($tlen < 0) { + throw new AMQPOutOfBoundsException('Table is longer than supported'); + } + + $table_data = new AMQPReader($this->rawread($tlen), null); + $result = $returnObject ? new AMQPTable() : array(); + while ($table_data->tell() < $tlen) { + $name = $table_data->read_shortstr(); + $ftype = AMQPAbstractCollection::getDataTypeForSymbol($ftypeSym = $table_data->rawread(1)); + $val = $table_data->read_value($ftype, $returnObject); + $returnObject ? $result->set($name, $val, $ftype) : $result[$name] = array($ftypeSym, $val); + } + + return $result; + } + + /** + * @return array|AMQPTable + */ + public function read_table_object() + { + return $this->read_table(true); + } + + /** + * Reads the array in the next value. + * + * @param bool $returnObject Whether to return AMQPArray instance instead of plain array + * @return array|AMQPArray + */ + public function read_array($returnObject = false) + { + $this->bitcount = $this->bits = 0; + + // Determine array length and its end position + $arrayLength = $this->read_php_int(); + $endOffset = $this->offset + $arrayLength; + + $result = $returnObject ? new AMQPArray() : array(); + // Read values until we reach the end of the array + while ($this->offset < $endOffset) { + $fieldType = AMQPAbstractCollection::getDataTypeForSymbol($this->rawread(1)); + $fieldValue = $this->read_value($fieldType, $returnObject); + $returnObject ? $result->push($fieldValue, $fieldType) : $result[] = $fieldValue; + } + + return $result; + } + + /** + * @return array|AMQPArray + */ + public function read_array_object() + { + return $this->read_array(true); + } + + /** + * Reads the next value as the provided field type. + * + * @param int $fieldType One of AMQPAbstractCollection::T_* constants + * @param bool $collectionsAsObjects Description + * @return mixed + * @throws \PhpAmqpLib\Exception\AMQPRuntimeException + */ + public function read_value($fieldType, $collectionsAsObjects = false) + { + $this->bitcount = $this->bits = 0; + + $val = null; + switch ($fieldType) { + case AMQPAbstractCollection::T_INT_SHORTSHORT: + //according to AMQP091 spec, 'b' is not bit, it is short-short-int, also valid for rabbit/qpid + //$val=$this->read_bit(); + $val = $this->read_signed_octet(); + break; + case AMQPAbstractCollection::T_INT_SHORTSHORT_U: + $val = $this->read_octet(); + break; + case AMQPAbstractCollection::T_INT_SHORT: + $val = $this->read_signed_short(); + break; + case AMQPAbstractCollection::T_INT_SHORT_U: + $val = $this->read_short(); + break; + case AMQPAbstractCollection::T_INT_LONG: + $val = $this->read_signed_long(); + break; + case AMQPAbstractCollection::T_INT_LONG_U: + $val = $this->read_long(); + break; + case AMQPAbstractCollection::T_INT_LONGLONG: + $val = $this->read_signed_longlong(); + break; + case AMQPAbstractCollection::T_INT_LONGLONG_U: + $val = $this->read_longlong(); + break; + case AMQPAbstractCollection::T_DECIMAL: + $e = $this->read_octet(); + $n = $this->read_signed_long(); + $val = new AMQPDecimal($n, $e); + break; + case AMQPAbstractCollection::T_TIMESTAMP: + $val = $this->read_timestamp(); + break; + case AMQPAbstractCollection::T_BOOL: + $val = $this->read_octet(); + break; + case AMQPAbstractCollection::T_STRING_SHORT: + $val = $this->read_shortstr(); + break; + case AMQPAbstractCollection::T_STRING_LONG: + $val = $this->read_longstr(); + break; + case AMQPAbstractCollection::T_ARRAY: + $val = $this->read_array($collectionsAsObjects); + break; + case AMQPAbstractCollection::T_TABLE: + $val = $this->read_table($collectionsAsObjects); + break; + case AMQPAbstractCollection::T_VOID: + $val = null; + break; + default: + throw new AMQPInvalidArgumentException(sprintf( + 'Unsupported type "%s"', + $fieldType + )); + } + + return $val; + } + + /** + * @return int + */ + protected function tell() + { + return $this->offset; + } + + /** + * Sets the timeout (second) + * + * @param $timeout + */ + public function setTimeout($timeout) + { + $this->timeout = $timeout; + } + + /** + * @return int + */ + public function getTimeout() + { + return $this->timeout; + } +} diff --git a/lib/PhpAmqpLib/Wire/AMQPTable.php b/lib/PhpAmqpLib/Wire/AMQPTable.php new file mode 100644 index 0000000000..99d4e4d343 --- /dev/null +++ b/lib/PhpAmqpLib/Wire/AMQPTable.php @@ -0,0 +1,35 @@ + 128)) { + throw new Exception\AMQPInvalidArgumentException('Table key must be non-empty string up to 128 chars in length'); + } + $this->setValue($val, $type, $key); + } +} diff --git a/lib/PhpAmqpLib/Wire/AMQPWriter.php b/lib/PhpAmqpLib/Wire/AMQPWriter.php new file mode 100644 index 0000000000..3ef4ab1bf9 --- /dev/null +++ b/lib/PhpAmqpLib/Wire/AMQPWriter.php @@ -0,0 +1,494 @@ +out = ''; + $this->bits = array(); + $this->bitcount = 0; + } + + /** + * Packs integer into raw byte string in big-endian order + * Supports positive and negative ints represented as PHP int or string (except scientific notation) + * + * Floats has some precision issues and so intentionally not supported. + * Beware that floats out of PHP_INT_MAX range will be represented in scientific (exponential) notation when casted to string + * + * @param int|string $x Value to pack + * @param int $bytes Must be multiply of 2 + * @return string + */ + private static function packBigEndian($x, $bytes) + { + if (($bytes <= 0) || ($bytes % 2)) { + throw new AMQPInvalidArgumentException(sprintf('Expected bytes count must be multiply of 2, %s given', $bytes)); + } + + $ox = $x; //purely for dbg purposes (overflow exception) + $isNeg = false; + if (is_int($x)) { + if ($x < 0) { + $isNeg = true; + $x = abs($x); + } + } elseif (is_string($x)) { + if (!is_numeric($x)) { + throw new AMQPInvalidArgumentException(sprintf('Unknown numeric string format: %s', $x)); + } + $x = preg_replace('/^-/', '', $x, 1, $isNeg); + } else { + throw new AMQPInvalidArgumentException('Only integer and numeric string values are supported'); + } + if ($isNeg) { + $x = bcadd($x, -1, 0); + } //in negative domain starting point is -1, not 0 + + $res = array(); + for ($b = 0; $b < $bytes; $b += 2) { + $chnk = (int) bcmod($x, 65536); + $x = bcdiv($x, 65536, 0); + $res[] = pack('n', $isNeg ? ~$chnk : $chnk); + } + if ($x || ($isNeg && ($chnk & 0x8000))) { + throw new AMQPOutOfBoundsException(sprintf('Overflow detected while attempting to pack %s into %s bytes', $ox, $bytes)); + } + + return implode(array_reverse($res)); + } + + private function flushbits() + { + if (!empty($this->bits)) { + $this->out .= implode('', array_map('chr', $this->bits)); + $this->bits = array(); + $this->bitcount = 0; + } + } + + /** + * Get what's been encoded so far. + */ + public function getvalue() + { + /* temporarily needed for compatibility with write_bit unit tests */ + if ($this->bitcount) { + $this->flushbits(); + } + + return $this->out; + } + + /** + * Write a plain PHP string, with no special encoding. + */ + public function write($s) + { + $this->out .= $s; + + return $this; + } + + /** + * Write a boolean value. + * (deprecated, use write_bits instead) + * + * @deprecated + * @param $b + * @return $this + */ + public function write_bit($b) + { + $b = (int) (bool) $b; + $shift = $this->bitcount % 8; + + if ($shift == 0) { + $last = 0; + } else { + $last = array_pop($this->bits); + } + + $last |= ($b << $shift); + array_push($this->bits, $last); + $this->bitcount += 1; + + return $this; + } + + /** + * Write multiple bits as an octet + * + * @param $bits + * @return $this + */ + public function write_bits($bits) + { + $value = 0; + + foreach ($bits as $n => $bit) { + $bit = $bit ? 1 : 0; + $value |= ($bit << $n); + } + + $this->out .= chr($value); + + return $this; + } + + /** + * Write an integer as an unsigned 8-bit value + * + * @param $n + * @return $this + * @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException + */ + public function write_octet($n) + { + if ($n < 0 || $n > 255) { + throw new AMQPInvalidArgumentException('Octet out of range: ' . $n); + } + + $this->out .= chr($n); + + return $this; + } + + public function write_signed_octet($n) + { + if (($n < -128) || ($n > 127)) { + throw new AMQPInvalidArgumentException('Signed octet out of range: ' . $n); + } + + $this->out .= pack('c', $n); + + return $this; + } + + /** + * Write an integer as an unsigned 16-bit value + * + * @param $n + * @return $this + * @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException + */ + public function write_short($n) + { + if ($n < 0 || $n > 65535) { + throw new AMQPInvalidArgumentException('Short out of range: ' . $n); + } + + $this->out .= pack('n', $n); + + return $this; + } + + public function write_signed_short($n) + { + if (($n < -32768) || ($n > 32767)) { + throw new AMQPInvalidArgumentException('Signed short out of range: ' . $n); + } + + $this->out .= $this->correctEndianness(pack('s', $n)); + + return $this; + } + + /** + * Write an integer as an unsigned 32-bit value + * + * @param $n + * @return $this + */ + public function write_long($n) + { + if (($n < 0) || ($n > 4294967295)) { + throw new AMQPInvalidArgumentException('Long out of range: ' . $n); + } + + //Numeric strings >PHP_INT_MAX on 32bit are casted to PHP_INT_MAX, damn PHP + if (!$this->is64bits && is_string($n)) { + $n = (float) $n; + } + $this->out .= pack('N', $n); + + return $this; + } + + /** + * @param $n + * @return $this + */ + private function write_signed_long($n) + { + if (($n < -2147483648) || ($n > 2147483647)) { + throw new AMQPInvalidArgumentException('Signed long out of range: ' . $n); + } + + //on my 64bit debian this approach is slightly faster than splitIntoQuads() + $this->out .= $this->correctEndianness(pack('l', $n)); + + return $this; + } + + /** + * Write an integer as an unsigned 64-bit value + * + * @param $n + * @return $this + */ + public function write_longlong($n) + { + if ($n < 0) { + throw new AMQPInvalidArgumentException('Longlong out of range: ' . $n); + } + + // if PHP_INT_MAX is big enough for that + // direct $n<=PHP_INT_MAX check is unreliable on 64bit (values close to max) due to limited float precision + if (bcadd($n, -PHP_INT_MAX, 0) <= 0) { + // trick explained in http://www.php.net/manual/fr/function.pack.php#109328 + if ($this->is64bits) { + list($hi, $lo) = $this->splitIntoQuads($n); + } else { + $hi = 0; + $lo = $n; + } //on 32bits hi quad is 0 a priori + $this->out .= pack('NN', $hi, $lo); + } else { + try { + $this->out .= self::packBigEndian($n, 8); + } catch (AMQPOutOfBoundsException $ex) { + throw new AMQPInvalidArgumentException('Longlong out of range: ' . $n, 0, $ex); + } + } + + return $this; + } + + public function write_signed_longlong($n) + { + if ((bcadd($n, PHP_INT_MAX, 0) >= -1) && (bcadd($n, -PHP_INT_MAX, 0) <= 0)) { + if ($this->is64bits) { + list($hi, $lo) = $this->splitIntoQuads($n); + } else { + $hi = $n < 0 ? -1 : 0; + $lo = $n; + } //0xffffffff for negatives + $this->out .= pack('NN', $hi, $lo); + } elseif ($this->is64bits) { + throw new AMQPInvalidArgumentException('Signed longlong out of range: ' . $n); + } else { + if (bcadd($n, '-9223372036854775807', 0) > 0) { + throw new AMQPInvalidArgumentException('Signed longlong out of range: ' . $n); + } + try { + //will catch only negative overflow, as values >9223372036854775807 are valid for 8bytes range (unsigned) + $this->out .= self::packBigEndian($n, 8); + } catch (AMQPOutOfBoundsException $ex) { + throw new AMQPInvalidArgumentException('Signed longlong out of range: ' . $n, 0, $ex); + } + } + + return $this; + } + + /** + * @param int $n + * @return array + */ + private function splitIntoQuads($n) + { + $n = (int) $n; + + return array($n >> 32, $n & 0x00000000ffffffff); + } + + /** + * Write a string up to 255 bytes long after encoding. + * Assume UTF-8 encoding + * + * @param $s + * @return $this + * @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException + */ + public function write_shortstr($s) + { + $len = mb_strlen($s, 'ASCII'); + if ($len > 255) { + throw new AMQPInvalidArgumentException('String too long'); + } + + $this->write_octet($len); + $this->out .= $s; + + return $this; + } + + /** + * Write a string up to 2**32 bytes long. Assume UTF-8 encoding + * + * @param $s + * @return $this + */ + public function write_longstr($s) + { + $this->write_long(mb_strlen($s, 'ASCII')); + $this->out .= $s; + + return $this; + } + + /** + * Supports the writing of Array types, so that you can implement + * array methods, like Rabbitmq's HA parameters + * + * @param AMQPArray|array $a Instance of AMQPArray or PHP array WITHOUT format hints (unlike write_table()) + * @return self + */ + public function write_array($a) + { + if (!($a instanceof AMQPArray)) { + $a = new AMQPArray($a); + } + $data = new AMQPWriter(); + + foreach ($a as $v) { + $data->write_value($v[0], $v[1]); + } + + $data = $data->getvalue(); + $this->write_long(mb_strlen($data, 'ASCII')); + $this->write($data); + + return $this; + } + + /** + * Write unix time_t value as 64 bit timestamp + * + * @param $v + * @return $this + */ + public function write_timestamp($v) + { + $this->write_longlong($v); + + return $this; + } + + /** + * Write PHP array, as table. Input array format: keys are strings, + * values are (type,value) tuples. + * + * @param AMQPTable|array $d Instance of AMQPTable or PHP array WITH format hints (unlike write_array()) + * @return self + * @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException + */ + public function write_table($d) + { + $typeIsSym = !($d instanceof AMQPTable); //purely for back-compat purposes + + $table_data = new AMQPWriter(); + foreach ($d as $k => $va) { + list($ftype, $v) = $va; + $table_data->write_shortstr($k); + $table_data->write_value($typeIsSym ? AMQPAbstractCollection::getDataTypeForSymbol($ftype) : $ftype, $v); + } + + $table_data = $table_data->getvalue(); + $this->write_long(mb_strlen($table_data, 'ASCII')); + $this->write($table_data); + + return $this; + } + + /** + * for compat with method mapping used by AMQPMessage + */ + public function write_table_object($d) + { + return $this->write_table($d); + } + + /** + * @param int $type One of AMQPAbstractCollection::T_* constants + * @param mixed $val + */ + private function write_value($type, $val) + { + //This will find appropriate symbol for given data type for currently selected protocol + //Also will raise an exception on unknown type + $this->write(AMQPAbstractCollection::getSymbolForDataType($type)); + + switch ($type) { + case AMQPAbstractCollection::T_INT_SHORTSHORT: + $this->write_signed_octet($val); + break; + case AMQPAbstractCollection::T_INT_SHORTSHORT_U: + $this->write_octet($val); + break; + case AMQPAbstractCollection::T_INT_SHORT: + $this->write_signed_short($val); + break; + case AMQPAbstractCollection::T_INT_SHORT_U: + $this->write_short($val); + break; + case AMQPAbstractCollection::T_INT_LONG: + $this->write_signed_long($val); + break; + case AMQPAbstractCollection::T_INT_LONG_U: + $this->write_long($val); + break; + case AMQPAbstractCollection::T_INT_LONGLONG: + $this->write_signed_longlong($val); + break; + case AMQPAbstractCollection::T_INT_LONGLONG_U: + $this->write_longlong($val); + break; + case AMQPAbstractCollection::T_DECIMAL: + $this->write_octet($val->e); + $this->write_signed_long($val->n); + break; + case AMQPAbstractCollection::T_TIMESTAMP: + $this->write_timestamp($val); + break; + case AMQPAbstractCollection::T_BOOL: + $this->write_octet($val ? 1 : 0); + break; + case AMQPAbstractCollection::T_STRING_SHORT: + $this->write_shortstr($val); + break; + case AMQPAbstractCollection::T_STRING_LONG: + $this->write_longstr($val); + break; + case AMQPAbstractCollection::T_ARRAY: + $this->write_array($val); + break; + case AMQPAbstractCollection::T_TABLE: + $this->write_table($val); + break; + case AMQPAbstractCollection::T_VOID: + break; + default: + throw new AMQPInvalidArgumentException(sprintf( + 'Unsupported type "%s"', + $type + )); + } + } +} diff --git a/lib/PhpAmqpLib/Wire/AbstractClient.php b/lib/PhpAmqpLib/Wire/AbstractClient.php new file mode 100644 index 0000000000..fd4362faa0 --- /dev/null +++ b/lib/PhpAmqpLib/Wire/AbstractClient.php @@ -0,0 +1,36 @@ +is64bits = PHP_INT_SIZE == 8; + + $tmp = unpack('S', "\x01\x00"); // to maintain 5.3 compatibility + $this->isLittleEndian = $tmp[1] == 1; + } + + /** + * Converts byte-string between native and network byte order, in both directions + * + * @param string $byteStr + * @return string + */ + protected function correctEndianness($byteStr) + { + return $this->isLittleEndian ? strrev($byteStr) : $byteStr; + } +} diff --git a/lib/PhpAmqpLib/Wire/Constants080.php b/lib/PhpAmqpLib/Wire/Constants080.php new file mode 100644 index 0000000000..4e856f1d19 --- /dev/null +++ b/lib/PhpAmqpLib/Wire/Constants080.php @@ -0,0 +1,142 @@ + 'FRAME-METHOD', + 2 => 'FRAME-HEADER', + 3 => 'FRAME-BODY', + 4 => 'FRAME-OOB-METHOD', + 5 => 'FRAME-OOB-HEADER', + 6 => 'FRAME-OOB-BODY', + 7 => 'FRAME-TRACE', + 8 => 'FRAME-HEARTBEAT', + 4096 => 'FRAME-MIN-SIZE', + 206 => 'FRAME-END', + 501 => 'FRAME-ERROR', + ); + + public static $CONTENT_METHODS = array( + 0 => '60,40', + 1 => '60,50', + 2 => '60,60', + 3 => '60,71', + 4 => '70,50', + 5 => '70,70', + 6 => '80,40', + 7 => '80,50', + 8 => '80,60', + 9 => '110,10', + 10 => '120,40', + 11 => '120,41', + ); + + public static $CLOSE_METHODS = array( + 0 => '10,60', + 1 => '20,40', + ); + + public static $GLOBAL_METHOD_NAMES = array( + '10,10' => 'Connection.start', + '10,11' => 'Connection.start_ok', + '10,20' => 'Connection.secure', + '10,21' => 'Connection.secure_ok', + '10,30' => 'Connection.tune', + '10,31' => 'Connection.tune_ok', + '10,40' => 'Connection.open', + '10,41' => 'Connection.open_ok', + '10,50' => 'Connection.redirect', + '10,60' => 'Connection.close', + '10,61' => 'Connection.close_ok', + '20,10' => 'Channel.open', + '20,11' => 'Channel.open_ok', + '20,20' => 'Channel.flow', + '20,21' => 'Channel.flow_ok', + '20,30' => 'Channel.alert', + '20,40' => 'Channel.close', + '20,41' => 'Channel.close_ok', + '30,10' => 'Access.request', + '30,11' => 'Access.request_ok', + '40,10' => 'Exchange.declare', + '40,11' => 'Exchange.declare_ok', + '40,20' => 'Exchange.delete', + '40,21' => 'Exchange.delete_ok', + '50,10' => 'Queue.declare', + '50,11' => 'Queue.declare_ok', + '50,20' => 'Queue.bind', + '50,21' => 'Queue.bind_ok', + '50,30' => 'Queue.purge', + '50,31' => 'Queue.purge_ok', + '50,40' => 'Queue.delete', + '50,41' => 'Queue.delete_ok', + '50,50' => 'Queue.unbind', + '50,51' => 'Queue.unbind_ok', + '60,10' => 'Basic.qos', + '60,11' => 'Basic.qos_ok', + '60,20' => 'Basic.consume', + '60,21' => 'Basic.consume_ok', + '60,30' => 'Basic.cancel', + '60,31' => 'Basic.cancel_ok', + '60,40' => 'Basic.publish', + '60,50' => 'Basic.return', + '60,60' => 'Basic.deliver', + '60,70' => 'Basic.get', + '60,71' => 'Basic.get_ok', + '60,72' => 'Basic.get_empty', + '60,80' => 'Basic.ack', + '60,90' => 'Basic.reject', + '60,100' => 'Basic.recover_async', + '60,110' => 'Basic.recover', + '60,111' => 'Basic.recover_ok', + '70,10' => 'File.qos', + '70,11' => 'File.qos_ok', + '70,20' => 'File.consume', + '70,21' => 'File.consume_ok', + '70,30' => 'File.cancel', + '70,31' => 'File.cancel_ok', + '70,40' => 'File.open', + '70,41' => 'File.open_ok', + '70,50' => 'File.stage', + '70,60' => 'File.publish', + '70,70' => 'File.return', + '70,80' => 'File.deliver', + '70,90' => 'File.ack', + '70,100' => 'File.reject', + '80,10' => 'Stream.qos', + '80,11' => 'Stream.qos_ok', + '80,20' => 'Stream.consume', + '80,21' => 'Stream.consume_ok', + '80,30' => 'Stream.cancel', + '80,31' => 'Stream.cancel_ok', + '80,40' => 'Stream.publish', + '80,50' => 'Stream.return', + '80,60' => 'Stream.deliver', + '90,10' => 'Tx.select', + '90,11' => 'Tx.select_ok', + '90,20' => 'Tx.commit', + '90,21' => 'Tx.commit_ok', + '90,30' => 'Tx.rollback', + '90,31' => 'Tx.rollback_ok', + '100,10' => 'Dtx.select', + '100,11' => 'Dtx.select_ok', + '100,20' => 'Dtx.start', + '100,21' => 'Dtx.start_ok', + '110,10' => 'Tunnel.request', + '120,10' => 'Test.integer', + '120,11' => 'Test.integer_ok', + '120,20' => 'Test.string', + '120,21' => 'Test.string_ok', + '120,30' => 'Test.table', + '120,31' => 'Test.table_ok', + '120,40' => 'Test.content', + '120,41' => 'Test.content_ok', + ); + +} diff --git a/lib/PhpAmqpLib/Wire/Constants091.php b/lib/PhpAmqpLib/Wire/Constants091.php new file mode 100644 index 0000000000..2cde3942ac --- /dev/null +++ b/lib/PhpAmqpLib/Wire/Constants091.php @@ -0,0 +1,101 @@ + 'FRAME-METHOD', + 2 => 'FRAME-HEADER', + 3 => 'FRAME-BODY', + 8 => 'FRAME-HEARTBEAT', + 4096 => 'FRAME-MIN-SIZE', + 206 => 'FRAME-END', + 501 => 'FRAME-ERROR', + ); + + public static $CONTENT_METHODS = array( + 0 => '60,40', + 1 => '60,50', + 2 => '60,60', + 3 => '60,71', + ); + + public static $CLOSE_METHODS = array( + 0 => '10,50', + 1 => '20,40', + ); + + public static $GLOBAL_METHOD_NAMES = array( + '10,10' => 'Connection.start', + '10,11' => 'Connection.start_ok', + '10,20' => 'Connection.secure', + '10,21' => 'Connection.secure_ok', + '10,30' => 'Connection.tune', + '10,31' => 'Connection.tune_ok', + '10,40' => 'Connection.open', + '10,41' => 'Connection.open_ok', + '10,50' => 'Connection.close', + '10,51' => 'Connection.close_ok', + '10,60' => 'Connection.blocked', + '10,61' => 'Connection.unblocked', + '20,10' => 'Channel.open', + '20,11' => 'Channel.open_ok', + '20,20' => 'Channel.flow', + '20,21' => 'Channel.flow_ok', + '20,40' => 'Channel.close', + '20,41' => 'Channel.close_ok', + '30,10' => 'Access.request', + '30,11' => 'Access.request_ok', + '40,10' => 'Exchange.declare', + '40,11' => 'Exchange.declare_ok', + '40,20' => 'Exchange.delete', + '40,21' => 'Exchange.delete_ok', + '40,30' => 'Exchange.bind', + '40,31' => 'Exchange.bind_ok', + '40,40' => 'Exchange.unbind', + '40,51' => 'Exchange.unbind_ok', + '50,10' => 'Queue.declare', + '50,11' => 'Queue.declare_ok', + '50,20' => 'Queue.bind', + '50,21' => 'Queue.bind_ok', + '50,30' => 'Queue.purge', + '50,31' => 'Queue.purge_ok', + '50,40' => 'Queue.delete', + '50,41' => 'Queue.delete_ok', + '50,50' => 'Queue.unbind', + '50,51' => 'Queue.unbind_ok', + '60,10' => 'Basic.qos', + '60,11' => 'Basic.qos_ok', + '60,20' => 'Basic.consume', + '60,21' => 'Basic.consume_ok', + '60,30' => 'Basic.cancel', + '60,31' => 'Basic.cancel_ok', + '60,40' => 'Basic.publish', + '60,50' => 'Basic.return', + '60,60' => 'Basic.deliver', + '60,70' => 'Basic.get', + '60,71' => 'Basic.get_ok', + '60,72' => 'Basic.get_empty', + '60,80' => 'Basic.ack', + '60,90' => 'Basic.reject', + '60,100' => 'Basic.recover_async', + '60,110' => 'Basic.recover', + '60,111' => 'Basic.recover_ok', + '60,120' => 'Basic.nack', + '90,10' => 'Tx.select', + '90,11' => 'Tx.select_ok', + '90,20' => 'Tx.commit', + '90,21' => 'Tx.commit_ok', + '90,30' => 'Tx.rollback', + '90,31' => 'Tx.rollback_ok', + '85,10' => 'Confirm.select', + '85,11' => 'Confirm.select_ok', + ); + +} diff --git a/lib/PhpAmqpLib/Wire/GenericContent.php b/lib/PhpAmqpLib/Wire/GenericContent.php new file mode 100644 index 0000000000..6058379874 --- /dev/null +++ b/lib/PhpAmqpLib/Wire/GenericContent.php @@ -0,0 +1,209 @@ + 'shortstr' + ); + + /** + * @param $props + * @param null $prop_types + */ + public function __construct($props, $prop_types = null) + { + if ($prop_types) { + $this->prop_types = $prop_types; + } else { + $this->prop_types = self::$PROPERTIES; + } + + if ($props) { + $this->properties = array_intersect_key($props, $this->prop_types); + } + } + + /** + * Check whether a property exists in the 'properties' dictionary + * or if present - in the 'delivery_info' dictionary. + * + * @param string $name + * @return bool + */ + public function has($name) + { + return isset($this->properties[$name]) || isset($this->delivery_info[$name]); + } + + /** + * Look for additional properties in the 'properties' dictionary, + * and if present - the 'delivery_info' dictionary. + * + * @param string $name + * @throws \OutOfBoundsException + * @return mixed|AMQPChannel + */ + public function get($name) + { + if (isset($this->properties[$name])) { + return $this->properties[$name]; + } + + if (isset($this->delivery_info[$name])) { + return $this->delivery_info[$name]; + } + + throw new \OutOfBoundsException(sprintf( + 'No "%s" property', + $name + )); + } + + /** + * Returns the properties content + * + * @return array + */ + public function get_properties() + { + return $this->properties; + } + + /** + * Sets a property value + * + * @param string $name The property name (one of the property definition) + * @param mixed $value The property value + * @throws \OutOfBoundsException + */ + public function set($name, $value) + { + if (!array_key_exists($name, $this->prop_types)) { + throw new \OutOfBoundsException(sprintf( + 'No "%s" property', + $name + )); + } + + $this->properties[$name] = $value; + } + + /** + * Given the raw bytes containing the property-flags and + * property-list from a content-frame-header, parse and insert + * into a dictionary stored in this object as an attribute named + * 'properties'. + * + * @param AMQPReader $r + * NOTE: do not mutate $reader + */ + public function load_properties($r) + { + // Read 16-bit shorts until we get one with a low bit set to zero + $flags = array(); + while (true) { + $flag_bits = $r->read_short(); + $flags[] = $flag_bits; + if (($flag_bits & 1) == 0) { + break; + } + } + + $shift = 0; + $d = array(); + foreach ($this->prop_types as $key => $proptype) { + if ($shift == 0) { + if (!$flags) { + break; + } + $flag_bits = array_shift($flags); + $shift = 15; + } + if ($flag_bits & (1 << $shift)) { + $d[$key] = $r->{'read_' . $proptype}(); + } + + $shift -= 1; + } + $this->properties = $d; + } + + + /** + * Serializes the 'properties' attribute (a dictionary) into the + * raw bytes making up a set of property flags and a property + * list, suitable for putting into a content frame header. + * + * @return string + * @todo Inject the AMQPWriter to make the method easier to test + */ + public function serialize_properties() + { + if (!empty($this->serialized_properties)) { + return $this->serialized_properties; + } + + $shift = 15; + $flag_bits = 0; + $flags = array(); + $raw_bytes = new AMQPWriter(); + + foreach ($this->prop_types as $key => $prototype) { + $val = isset($this->properties[$key]) ? $this->properties[$key] : null; + + // Very important: PHP type eval is weak, use the === to test the + // value content. Zero or false value should not be removed + if ($val === null) { + $shift -= 1; + continue; + } + + if ($shift === 0) { + $flags[] = $flag_bits; + $flag_bits = 0; + $shift = 15; + } + + $flag_bits |= (1 << $shift); + if ($prototype != 'bit') { + $raw_bytes->{'write_' . $prototype}($val); + } + + $shift -= 1; + } + + $flags[] = $flag_bits; + $result = new AMQPWriter(); + foreach ($flags as $flag_bits) { + $result->write_short($flag_bits); + } + + $result->write($raw_bytes->getvalue()); + + $this->serialized_properties = $result->getvalue(); + + return $this->serialized_properties; + } +} diff --git a/lib/PhpAmqpLib/Wire/IO/AbstractIO.php b/lib/PhpAmqpLib/Wire/IO/AbstractIO.php new file mode 100644 index 0000000000..0822b9cbf5 --- /dev/null +++ b/lib/PhpAmqpLib/Wire/IO/AbstractIO.php @@ -0,0 +1,44 @@ +host = $host; + $this->port = $port; + $this->timeout = $timeout; + $this->keepalive = $keepalive; + } + + /** + * Sets up the socket connection + * + * @throws \Exception + */ + public function connect() + { + $this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + + socket_set_option($this->sock, SOL_SOCKET, SO_RCVTIMEO, array('sec' => $this->timeout, 'usec' => 0)); + socket_set_option($this->sock, SOL_SOCKET, SO_SNDTIMEO, array('sec' => $this->timeout, 'usec' => 0)); + + if (!socket_connect($this->sock, $this->host, $this->port)) { + $errno = socket_last_error($this->sock); + $errstr = socket_strerror($errno); + throw new AMQPIOException(sprintf( + 'Error Connecting to server (%s): %s', + $errno, + $errstr + ), $errno); + } + + socket_set_block($this->sock); + socket_set_option($this->sock, SOL_TCP, TCP_NODELAY, 1); + + if ($this->keepalive) { + $this->enable_keepalive(); + } + } + + /** + * @return resource + */ + public function getSocket() + { + return $this->sock; + } + + /** + * Reconnects the socket + */ + public function reconnect() + { + $this->close(); + $this->connect(); + } + + /** + * @param $n + * @return mixed|string + * @throws \PhpAmqpLib\Exception\AMQPIOException + * @throws \PhpAmqpLib\Exception\AMQPRuntimeException + */ + public function read($n) + { + $res = ''; + $read = 0; + + $buf = socket_read($this->sock, $n); + while ($read < $n && $buf !== '' && $buf !== false) { + // Null sockets are invalid, throw exception + if (is_null($this->sock)) { + throw new AMQPRuntimeException(sprintf( + 'Socket was null! Last SocketError was: %s', + socket_strerror(socket_last_error()) + )); + } + + $read += mb_strlen($buf, 'ASCII'); + $res .= $buf; + $buf = socket_read($this->sock, $n - $read); + } + + if (mb_strlen($res, 'ASCII') != $n) { + throw new AMQPIOException(sprintf( + 'Error reading data. Received %s instead of expected %s bytes', + mb_strlen($res, 'ASCII'), + $n + )); + } + + return $res; + } + + /** + * @param $data + * @return mixed|void + * @throws \PhpAmqpLib\Exception\AMQPIOException + * @throws \PhpAmqpLib\Exception\AMQPRuntimeException + */ + public function write($data) + { + $len = mb_strlen($data, 'ASCII'); + + while (true) { + // Null sockets are invalid, throw exception + if (is_null($this->sock)) { + throw new AMQPRuntimeException(sprintf( + 'Socket was null! Last SocketError was: %s', + socket_strerror(socket_last_error()) + )); + } + + $sent = socket_write($this->sock, $data, $len); + if ($sent === false) { + throw new AMQPIOException(sprintf( + 'Error sending data. Last SocketError: %s', + socket_strerror(socket_last_error()) + )); + } + + // Check if the entire message has been sent + if ($sent < $len) { + // If not sent the entire message. + // Get the part of the message that has not yet been sent as message + $data = mb_substr($data, $sent, mb_strlen($data, 'ASCII') - $sent, 'ASCII'); + // Get the length of the not sent part + $len -= $sent; + } else { + break; + } + } + } + + public function close() + { + if (is_resource($this->sock)) { + socket_close($this->sock); + } + $this->sock = null; + } + + /** + * @param $sec + * @param $usec + * @return int|mixed + */ + public function select($sec, $usec) + { + $read = array($this->sock); + $write = null; + $except = null; + + return socket_select($read, $write, $except, $sec, $usec); + } + + /** + * @throws \PhpAmqpLib\Exception\AMQPIOException + */ + protected function enable_keepalive() + { + if (!defined('SOL_SOCKET') || !defined('SO_KEEPALIVE')) { + throw new AMQPIOException('Can not enable keepalive: SOL_SOCKET or SO_KEEPALIVE is not defined'); + } + + socket_set_option($this->sock, SOL_SOCKET, SO_KEEPALIVE, 1); + } +} diff --git a/lib/PhpAmqpLib/Wire/IO/StreamIO.php b/lib/PhpAmqpLib/Wire/IO/StreamIO.php new file mode 100644 index 0000000000..0ec12fc839 --- /dev/null +++ b/lib/PhpAmqpLib/Wire/IO/StreamIO.php @@ -0,0 +1,309 @@ +host = $host; + $this->port = $port; + $this->connection_timeout = $connection_timeout; + $this->read_write_timeout = $read_write_timeout; + $this->context = $context; + $this->keepalive = $keepalive; + $this->heartbeat = $heartbeat; + $this->canDispatchPcntlSignal = extension_loaded('pcntl') && function_exists('pcntl_signal_dispatch') + && (defined('AMQP_WITHOUT_SIGNALS') ? !AMQP_WITHOUT_SIGNALS : true); + } + + /** + * Sets up the stream connection + * + * @throws \PhpAmqpLib\Exception\AMQPRuntimeException + * @throws \Exception + */ + public function connect() + { + $errstr = $errno = null; + + if ($this->context) { + $remote = sprintf('ssl://%s:%s', $this->host, $this->port); + $this->sock = @stream_socket_client( + $remote, + $errno, + $errstr, + $this->connection_timeout, + STREAM_CLIENT_CONNECT, + $this->context + ); + } else { + $remote = sprintf('tcp://%s:%s', $this->host, $this->port); + $this->sock = @stream_socket_client( + $remote, + $errno, + $errstr, + $this->connection_timeout, + STREAM_CLIENT_CONNECT + ); + } + + if (!$this->sock) { + throw new AMQPRuntimeException(sprintf( + 'Error Connecting to server (%s): %s', + $errno, + $errstr + ), $errno); + } + + list($sec, $uSec) = MiscHelper::splitSecondsMicroseconds($this->read_write_timeout); + if (!stream_set_timeout($this->sock, $sec, $uSec)) { + throw new AMQPIOException('Timeout could not be set'); + } + + stream_set_blocking($this->sock, 1); + + if ($this->keepalive) { + $this->enable_keepalive(); + } + } + + /** + * Reconnects the socket + */ + public function reconnect() + { + $this->close(); + $this->connect(); + } + + /** + * @param $n + * @throws \PhpAmqpLib\Exception\AMQPIOException + * @return mixed|string + */ + public function read($n) + { + $res = ''; + $read = 0; + + while ($read < $n && !feof($this->sock) && (false !== ($buf = fread($this->sock, $n - $read)))) { + $this->check_heartbeat(); + + if ($buf === '') { + if ($this->canDispatchPcntlSignal) { + pcntl_signal_dispatch(); + } + continue; + } + + $read += mb_strlen($buf, 'ASCII'); + $res .= $buf; + + $this->last_read = microtime(true); + } + + if (mb_strlen($res, 'ASCII') != $n) { + throw new AMQPIOException(sprintf( + 'Error reading data. Received %s instead of expected %s bytes', + mb_strlen($res, 'ASCII'), + $n + )); + } + + return $res; + } + + /** + * @param $data + * @return mixed|void + * @throws \PhpAmqpLib\Exception\AMQPRuntimeException + * @throws \PhpAmqpLib\Exception\AMQPTimeoutException + */ + public function write($data) + { + $len = mb_strlen($data, 'ASCII'); + while (true) { + if (is_null($this->sock)) { + throw new AMQPRuntimeException('Broken pipe or closed connection'); + } + + if (false === ($written = @fwrite($this->sock, $data))) { + throw new AMQPRuntimeException('Error sending data'); + } + + if ($written === 0) { + throw new AMQPRuntimeException('Broken pipe or closed connection'); + } + + if ($this->timed_out()) { + throw new AMQPTimeoutException('Error sending data. Socket connection timed out'); + } + + $len = $len - $written; + if ($len > 0) { + $data = mb_substr($data, 0 - $len, 0 - $len, 'ASCII'); + } else { + $this->last_write = microtime(true); + break; + } + } + } + + /** + * Heartbeat logic: check connection health here + */ + protected function check_heartbeat() + { + // ignore unless heartbeat interval is set + if ($this->heartbeat !== 0 && $this->last_read && $this->last_write) { + $t = microtime(true); + $t_read = round($t - $this->last_read); + $t_write = round($t - $this->last_write); + + // server has gone away + if (($this->heartbeat * 2) < $t_read) { + $this->reconnect(); + } + + // time for client to send a heartbeat + if (($this->heartbeat / 2) < $t_write) { + $this->write_heartbeat(); + } + } + } + + /** + * Sends a heartbeat message + */ + protected function write_heartbeat() + { + $pkt = new AMQPWriter(); + $pkt->write_octet(8); + $pkt->write_short(0); + $pkt->write_long(0); + $pkt->write_octet(0xCE); + $val = $pkt->getvalue(); + $this->write($pkt->getvalue()); + } + + public function close() + { + if (is_resource($this->sock)) { + fclose($this->sock); + } + $this->sock = null; + } + + /** + * @return resource + */ + public function get_socket() + { + return $this->sock; + } + + /** + * @return resource + */ + public function getSocket() + { + return $this->get_socket(); + } + + /** + * @param $sec + * @param $usec + * @return int|mixed + */ + public function select($sec, $usec) + { + $read = array($this->sock); + $write = null; + $except = null; + + return stream_select($read, $write, $except, $sec, $usec); + } + + /** + * @return mixed + */ + protected function timed_out() + { + // get status of socket to determine whether or not it has timed out + $info = stream_get_meta_data($this->sock); + + return $info['timed_out']; + } + + /** + * @throws \PhpAmqpLib\Exception\AMQPIOException + */ + protected function enable_keepalive() + { + if (!function_exists('socket_import_stream')) { + throw new AMQPIOException('Can not enable keepalive: function socket_import_stream does not exist'); + } + + if (!defined('SOL_SOCKET') || !defined('SO_KEEPALIVE')) { + throw new AMQPIOException('Can not enable keepalive: SOL_SOCKET or SO_KEEPALIVE is not defined'); + } + + $socket = socket_import_stream($this->sock); + socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1); + } +} diff --git a/lib/PhpAmqpLib/autoload.php b/lib/PhpAmqpLib/autoload.php new file mode 100644 index 0000000000..67f05e8316 --- /dev/null +++ b/lib/PhpAmqpLib/autoload.php @@ -0,0 +1,64 @@ + '/Channel/AbstractChannel.php', + 'phpamqplib\\channel\\amqpchannel' => '/Channel/AMQPChannel.php', + 'phpamqplib\\connection\\abstractconnection' => '/Connection/AbstractConnection.php', + 'phpamqplib\\connection\\amqpconnection' => '/Connection/AMQPConnection.php', + 'phpamqplib\\connection\\amqplazyconnection' => '/Connection/AMQPLazyConnection.php', + 'phpamqplib\\connection\\amqpsocketconnection' => '/Connection/AMQPSocketConnection.php', + 'phpamqplib\\connection\\amqpsslconnection' => '/Connection/AMQPSSLConnection.php', + 'phpamqplib\\connection\\amqpstreamconnection' => '/Connection/AMQPStreamConnection.php', + 'phpamqplib\\exception\\amqpbasiccancelexception' => '/Exception/AMQPBasicCancelException.php', + 'phpamqplib\\exception\\amqpchannelexception' => '/Exception/AMQPChannelException.php', + 'phpamqplib\\exception\\amqpconnectionexception' => '/Exception/AMQPConnectionException.php', + 'phpamqplib\\exception\\amqpexception' => '/Exception/AMQPException.php', + 'phpamqplib\\exception\\amqpexceptioninterface' => '/Exception/AMQPExceptionInterface.php', + 'phpamqplib\\exception\\amqpinvalidargumentexception' => '/Exception/AMQPInvalidArgumentException.php', + 'phpamqplib\\exception\\amqpioexception' => '/Exception/AMQPIOException.php', + 'phpamqplib\\exception\\amqplogicexception' => '/Exception/AMQPLogicException.php', + 'phpamqplib\\exception\\amqpoutofboundsexception' => '/Exception/AMQPOutOfBoundsException.php', + 'phpamqplib\\exception\\amqpoutofrangeexception' => '/Exception/AMQPOutOfRangeException.php', + 'phpamqplib\\exception\\amqpprotocolchannelexception' => '/Exception/AMQPProtocolChannelException.php', + 'phpamqplib\\exception\\amqpprotocolconnectionexception' => '/Exception/AMQPProtocolConnectionException.php', + 'phpamqplib\\exception\\amqpprotocolexception' => '/Exception/AMQPProtocolException.php', + 'phpamqplib\\exception\\amqpruntimeexception' => '/Exception/AMQPRuntimeException.php', + 'phpamqplib\\exception\\amqptimeoutexception' => '/Exception/AMQPTimeoutException.php', + 'phpamqplib\\helper\\mischelper' => '/Helper/MiscHelper.php', + 'phpamqplib\\helper\\protocol\\methodmap080' => '/Helper/Protocol/MethodMap080.php', + 'phpamqplib\\helper\\protocol\\methodmap091' => '/Helper/Protocol/MethodMap091.php', + 'phpamqplib\\helper\\protocol\\protocol080' => '/Helper/Protocol/Protocol080.php', + 'phpamqplib\\helper\\protocol\\protocol091' => '/Helper/Protocol/Protocol091.php', + 'phpamqplib\\helper\\protocol\\wait080' => '/Helper/Protocol/Wait080.php', + 'phpamqplib\\helper\\protocol\\wait091' => '/Helper/Protocol/Wait091.php', + 'phpamqplib\\message\\amqpmessage' => '/Message/AMQPMessage.php', + 'phpamqplib\\wire\\abstractclient' => '/Wire/AbstractClient.php', + 'phpamqplib\\wire\\amqpabstractcollection' => '/Wire/AMQPAbstractCollection.php', + 'phpamqplib\\wire\\amqparray' => '/Wire/AMQPArray.php', + 'phpamqplib\\wire\\amqpdecimal' => '/Wire/AMQPDecimal.php', + 'phpamqplib\\wire\\amqpreader' => '/Wire/AMQPReader.php', + 'phpamqplib\\wire\\amqptable' => '/Wire/AMQPTable.php', + 'phpamqplib\\wire\\amqpwriter' => '/Wire/AMQPWriter.php', + 'phpamqplib\\wire\\constants080' => '/Wire/Constants080.php', + 'phpamqplib\\wire\\constants091' => '/Wire/Constants091.php', + 'phpamqplib\\wire\\genericcontent' => '/Wire/GenericContent.php', + 'phpamqplib\\wire\\io\\abstractio' => '/Wire/IO/AbstractIO.php', + 'phpamqplib\\wire\\io\\socketio' => '/Wire/IO/SocketIO.php', + 'phpamqplib\\wire\\io\\streamio' => '/Wire/IO/StreamIO.php' + ); + } + $cn = strtolower($class); + if (isset($classes[$cn])) { + require __DIR__ . $classes[$cn]; + } + }, + true, + false +); +// @codeCoverageIgnoreEnd