bunny 0.8.0 → 0.9.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +7 -1
- data/.travis.yml +14 -4
- data/ChangeLog.md +72 -0
- data/Gemfile +17 -11
- data/README.md +82 -0
- data/bunny.gemspec +6 -13
- data/examples/connection/heartbeat.rb +17 -0
- data/lib/bunny.rb +40 -56
- data/lib/bunny/channel.rb +615 -19
- data/lib/bunny/channel_id_allocator.rb +59 -0
- data/lib/bunny/compatibility.rb +24 -0
- data/lib/bunny/concurrent/condition.rb +63 -0
- data/lib/bunny/consumer.rb +42 -26
- data/lib/bunny/consumer_tag_generator.rb +22 -0
- data/lib/bunny/consumer_work_pool.rb +67 -0
- data/lib/bunny/exceptions.rb +128 -0
- data/lib/bunny/exchange.rb +131 -136
- data/lib/bunny/framing.rb +53 -0
- data/lib/bunny/heartbeat_sender.rb +59 -0
- data/lib/bunny/main_loop.rb +70 -0
- data/lib/bunny/message_metadata.rb +126 -0
- data/lib/bunny/queue.rb +102 -275
- data/lib/bunny/session.rb +478 -0
- data/lib/bunny/socket.rb +44 -0
- data/lib/bunny/system_timer.rb +9 -9
- data/lib/bunny/transport.rb +179 -0
- data/lib/bunny/version.rb +1 -1
- data/spec/compatibility/queue_declare_spec.rb +40 -0
- data/spec/higher_level_api/integration/basic_ack_spec.rb +54 -0
- data/spec/higher_level_api/integration/basic_consume_spec.rb +51 -0
- data/spec/higher_level_api/integration/basic_get_spec.rb +47 -0
- data/spec/higher_level_api/integration/basic_nack_spec.rb +39 -0
- data/spec/higher_level_api/integration/basic_publish_spec.rb +105 -0
- data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -0
- data/spec/higher_level_api/integration/basic_recover_spec.rb +18 -0
- data/spec/higher_level_api/integration/basic_reject_spec.rb +53 -0
- data/spec/higher_level_api/integration/basic_return_spec.rb +33 -0
- data/spec/higher_level_api/integration/channel_close_spec.rb +29 -0
- data/spec/higher_level_api/integration/channel_flow_spec.rb +24 -0
- data/spec/higher_level_api/integration/channel_open_spec.rb +57 -0
- data/spec/higher_level_api/integration/channel_open_stress_spec.rb +22 -0
- data/spec/higher_level_api/integration/confirm_select_spec.rb +19 -0
- data/spec/higher_level_api/integration/connection_spec.rb +340 -0
- data/spec/higher_level_api/integration/exchange_bind_spec.rb +31 -0
- data/spec/higher_level_api/integration/exchange_declare_spec.rb +183 -0
- data/spec/higher_level_api/integration/exchange_delete_spec.rb +37 -0
- data/spec/higher_level_api/integration/exchange_unbind_spec.rb +40 -0
- data/spec/higher_level_api/integration/queue_bind_spec.rb +109 -0
- data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -0
- data/spec/higher_level_api/integration/queue_delete_spec.rb +38 -0
- data/spec/higher_level_api/integration/queue_purge_spec.rb +30 -0
- data/spec/higher_level_api/integration/queue_unbind_spec.rb +33 -0
- data/spec/higher_level_api/integration/tx_commit_spec.rb +21 -0
- data/spec/higher_level_api/integration/tx_rollback_spec.rb +21 -0
- data/spec/lower_level_api/integration/basic_cancel_spec.rb +57 -0
- data/spec/lower_level_api/integration/basic_consume_spec.rb +100 -0
- data/spec/spec_helper.rb +64 -0
- data/spec/unit/bunny_spec.rb +15 -0
- data/spec/unit/concurrent/condition_spec.rb +66 -0
- metadata +135 -93
- data/CHANGELOG +0 -21
- data/README.textile +0 -76
- data/Rakefile +0 -14
- data/examples/simple.rb +0 -32
- data/examples/simple_ack.rb +0 -35
- data/examples/simple_consumer.rb +0 -55
- data/examples/simple_fanout.rb +0 -41
- data/examples/simple_headers.rb +0 -42
- data/examples/simple_publisher.rb +0 -29
- data/examples/simple_topic.rb +0 -61
- data/ext/amqp-0.9.1.json +0 -389
- data/ext/config.yml +0 -4
- data/ext/qparser.rb +0 -426
- data/lib/bunny/client.rb +0 -370
- data/lib/bunny/subscription.rb +0 -92
- data/lib/qrack/amq-client-url.rb +0 -165
- data/lib/qrack/channel.rb +0 -20
- data/lib/qrack/client.rb +0 -247
- data/lib/qrack/errors.rb +0 -5
- data/lib/qrack/protocol/protocol.rb +0 -135
- data/lib/qrack/protocol/spec.rb +0 -525
- data/lib/qrack/qrack.rb +0 -20
- data/lib/qrack/queue.rb +0 -40
- data/lib/qrack/subscription.rb +0 -152
- data/lib/qrack/transport/buffer.rb +0 -305
- data/lib/qrack/transport/frame.rb +0 -102
- data/spec/spec_09/amqp_url_spec.rb +0 -19
- data/spec/spec_09/bunny_spec.rb +0 -76
- data/spec/spec_09/connection_spec.rb +0 -34
- data/spec/spec_09/exchange_spec.rb +0 -173
- data/spec/spec_09/queue_spec.rb +0 -240
    
        data/lib/qrack/qrack.rb
    DELETED
    
    | @@ -1,20 +0,0 @@ | |
| 1 | 
            -
            # encoding: utf-8
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            $: << File.expand_path(File.dirname(__FILE__))
         | 
| 4 | 
            -
             | 
| 5 | 
            -
            require 'protocol/spec'
         | 
| 6 | 
            -
            require 'protocol/protocol'
         | 
| 7 | 
            -
             | 
| 8 | 
            -
            require 'transport/buffer'
         | 
| 9 | 
            -
            require 'transport/frame'
         | 
| 10 | 
            -
             | 
| 11 | 
            -
            require 'qrack/client'
         | 
| 12 | 
            -
            require 'qrack/channel'
         | 
| 13 | 
            -
            require 'qrack/queue'
         | 
| 14 | 
            -
            require 'bunny/consumer'
         | 
| 15 | 
            -
            require 'qrack/errors'
         | 
| 16 | 
            -
             | 
| 17 | 
            -
            module Qrack
         | 
| 18 | 
            -
              include Protocol
         | 
| 19 | 
            -
              include Transport
         | 
| 20 | 
            -
            end
         | 
    
        data/lib/qrack/queue.rb
    DELETED
    
    | @@ -1,40 +0,0 @@ | |
| 1 | 
            -
            # encoding: utf-8
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            module Qrack
         | 
| 4 | 
            -
             | 
| 5 | 
            -
              # Queue ancestor class
         | 
| 6 | 
            -
              class Queue
         | 
| 7 | 
            -
             | 
| 8 | 
            -
                # @return [AMQ::Client::Consumer] Default consumer (registered with {Queue#subscribe}).
         | 
| 9 | 
            -
                attr_accessor :default_consumer
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                attr_reader :name, :client
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                attr_accessor :delivery_tag
         | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
                # Returns consumer count from {Queue#status}.
         | 
| 17 | 
            -
                def consumer_count
         | 
| 18 | 
            -
                  s = status
         | 
| 19 | 
            -
                  s[:consumer_count]
         | 
| 20 | 
            -
                end
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                # Returns message count from {Queue#status}.
         | 
| 23 | 
            -
                def message_count
         | 
| 24 | 
            -
                  s = status
         | 
| 25 | 
            -
                  s[:message_count]
         | 
| 26 | 
            -
                end
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                # Publishes a message to the queue via the default nameless '' direct exchange.
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                # @return [NilClass] nil
         | 
| 31 | 
            -
                # @deprecated
         | 
| 32 | 
            -
                # @note This method will be removed before 0.8 release.
         | 
| 33 | 
            -
                def publish(data, opts = {})
         | 
| 34 | 
            -
                  Bunny.deprecation_warning("Qrack::Queue#publish", "0.8", "Use direct_exchange = bunny.exchange(''); direct_exchange.publish('message', key: queue.name) if you want to publish directly to one given queue. For more informations see https://github.com/ruby-amqp/bunny/issues/15 and for more theoretical explanation check http://bit.ly/nOF1CK")
         | 
| 35 | 
            -
                  exchange.publish(data, opts)
         | 
| 36 | 
            -
                end
         | 
| 37 | 
            -
             | 
| 38 | 
            -
              end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
            end
         | 
    
        data/lib/qrack/subscription.rb
    DELETED
    
    | @@ -1,152 +0,0 @@ | |
| 1 | 
            -
            # encoding: utf-8
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            #################################################
         | 
| 4 | 
            -
            # WARNING: THIS CLASS IS DEPRECATED, DO NOT     #
         | 
| 5 | 
            -
            # USE IT DIRECTLY! USE BUNNY::CONSUMER INSTEAD! #
         | 
| 6 | 
            -
            #################################################
         | 
| 7 | 
            -
             | 
| 8 | 
            -
            module Qrack
         | 
| 9 | 
            -
              # Subscription ancestor class
         | 
| 10 | 
            -
              # @deprecated
         | 
| 11 | 
            -
              class Subscription
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                attr_accessor :consumer_tag, :delivery_tag, :message_max, :timeout, :ack, :exclusive
         | 
| 14 | 
            -
                attr_reader :client, :queue, :message_count
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                def initialize(client, queue, opts = {})
         | 
| 17 | 
            -
                  @client = client
         | 
| 18 | 
            -
                  @queue = queue
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                  # Get timeout value
         | 
| 21 | 
            -
                  @timeout = opts[:timeout] || nil
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                  # Get maximum amount of messages to process
         | 
| 24 | 
            -
                  @message_max = opts[:message_max] || nil
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                  # If a consumer tag is not passed in the server will generate one
         | 
| 27 | 
            -
                  @consumer_tag = opts[:consumer_tag] || nil
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                  # Ignore the :nowait option if passed, otherwise program will hang waiting for a
         | 
| 30 | 
            -
                  # response from the server causing an error.
         | 
| 31 | 
            -
                  opts.delete(:nowait)
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                  # Do we want to have to provide an acknowledgement?
         | 
| 34 | 
            -
                  @ack = opts[:ack] || nil
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                  # Does this consumer want exclusive use of the queue?
         | 
| 37 | 
            -
                  @exclusive = opts[:exclusive] || false
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                  # Initialize message counter
         | 
| 40 | 
            -
                  @message_count = 0
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                  # Store cancellator
         | 
| 43 | 
            -
                  @cancellator = opts[:cancellator]
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                  # Store options
         | 
| 46 | 
            -
                  @opts = opts
         | 
| 47 | 
            -
                end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                def start(&blk)
         | 
| 50 | 
            -
                  # Do not process any messages if zero message_max
         | 
| 51 | 
            -
                  if message_max == 0
         | 
| 52 | 
            -
                    return
         | 
| 53 | 
            -
                  end
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                  # Notify server about new consumer
         | 
| 56 | 
            -
                  setup_consumer
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                  # We need to keep track of three possible subscription states
         | 
| 59 | 
            -
                  # :subscribed, :pending, and :unsubscribed
         | 
| 60 | 
            -
                  # 'pending' occurs because of network latency, where we tried to unsubscribe but were already given a message
         | 
| 61 | 
            -
                  subscribe_state = :subscribed
         | 
| 62 | 
            -
             | 
| 63 | 
            -
                  # Start subscription loop
         | 
| 64 | 
            -
                  loop do
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                    begin
         | 
| 67 | 
            -
                      method = client.next_method(:timeout => timeout, :cancellator => @cancellator)
         | 
| 68 | 
            -
                    rescue Qrack::FrameTimeout
         | 
| 69 | 
            -
                      begin
         | 
| 70 | 
            -
                        queue.unsubscribe
         | 
| 71 | 
            -
                        subscribe_state = :unsubscribed
         | 
| 72 | 
            -
             | 
| 73 | 
            -
                        break
         | 
| 74 | 
            -
                      rescue Bunny::ProtocolError
         | 
| 75 | 
            -
                        # Unsubscribe failed because we actually got a message, so we're in a weird state.
         | 
| 76 | 
            -
                        # We have to keep processing the message or else it may be lost...
         | 
| 77 | 
            -
                        # ...and there is also a CancelOk method floating around that we need to consume from the socket
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                        method = client.last_method
         | 
| 80 | 
            -
                        subscribe_state = :pending
         | 
| 81 | 
            -
                      end
         | 
| 82 | 
            -
                    end
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                    # Increment message counter
         | 
| 85 | 
            -
                    @message_count += 1
         | 
| 86 | 
            -
             | 
| 87 | 
            -
                    # get delivery tag to use for acknowledge
         | 
| 88 | 
            -
                    queue.delivery_tag = method.delivery_tag if @ack
         | 
| 89 | 
            -
                    header = client.next_payload
         | 
| 90 | 
            -
             | 
| 91 | 
            -
                    # The unsubscribe ok may be sprinked into the payload
         | 
| 92 | 
            -
                    if subscribe_state == :pending and header.is_a?(Qrack::Protocol::Basic::CancelOk)
         | 
| 93 | 
            -
                      # We popped off the CancelOk, so we don't have to keep looking for it
         | 
| 94 | 
            -
                      subscribe_state = :unsubscribed
         | 
| 95 | 
            -
             | 
| 96 | 
            -
                      # Get the actual header now
         | 
| 97 | 
            -
                      header = client.next_payload
         | 
| 98 | 
            -
                    end
         | 
| 99 | 
            -
             | 
| 100 | 
            -
                    # If maximum frame size is smaller than message payload body then message
         | 
| 101 | 
            -
                    # will have a message header and several message bodies
         | 
| 102 | 
            -
                    msg = ''
         | 
| 103 | 
            -
                    while msg.length < header.size
         | 
| 104 | 
            -
                      message = client.next_payload
         | 
| 105 | 
            -
             | 
| 106 | 
            -
                      # The unsubscribe ok may be sprinked into the payload
         | 
| 107 | 
            -
                      if subscribe_state == :pending and message.is_a?(Qrack::Protocol::Basic::CancelOk)
         | 
| 108 | 
            -
                        # We popped off the CancelOk, so we don't have to keep looking for it
         | 
| 109 | 
            -
                        subscribe_state = :unsubscribed
         | 
| 110 | 
            -
                        next
         | 
| 111 | 
            -
                      end
         | 
| 112 | 
            -
             | 
| 113 | 
            -
                      msg << message
         | 
| 114 | 
            -
                    end
         | 
| 115 | 
            -
             | 
| 116 | 
            -
                    # If block present, pass the message info to the block for processing
         | 
| 117 | 
            -
                    blk.call({:header => header, :payload => msg, :delivery_details => method.arguments}) if !blk.nil?
         | 
| 118 | 
            -
             | 
| 119 | 
            -
                    # Unsubscribe if we've encountered the maximum number of messages
         | 
| 120 | 
            -
                    if subscribe_state == :subscribed and !message_max.nil? and message_count == message_max
         | 
| 121 | 
            -
                      queue.unsubscribe
         | 
| 122 | 
            -
                      subscribe_state = :unsubscribed
         | 
| 123 | 
            -
                    end
         | 
| 124 | 
            -
             | 
| 125 | 
            -
                    # Exit the loop if we've unsubscribed
         | 
| 126 | 
            -
                    if subscribe_state != :subscribed
         | 
| 127 | 
            -
                      # We still haven't found the CancelOk, so it's the next method
         | 
| 128 | 
            -
                      if subscribe_state == :pending
         | 
| 129 | 
            -
                        method = client.next_method
         | 
| 130 | 
            -
                        client.check_response(method, Qrack::Protocol::Basic::CancelOk, "Error unsubscribing from queue #{queue.name}, got #{method.class}")
         | 
| 131 | 
            -
             | 
| 132 | 
            -
                        subscribe_state = :unsubscribed
         | 
| 133 | 
            -
                      end
         | 
| 134 | 
            -
             | 
| 135 | 
            -
                      # Acknowledge receipt of the final message
         | 
| 136 | 
            -
                      queue.ack() if @ack
         | 
| 137 | 
            -
             | 
| 138 | 
            -
                      # Quit the loop
         | 
| 139 | 
            -
                      break
         | 
| 140 | 
            -
                    end
         | 
| 141 | 
            -
             | 
| 142 | 
            -
                    # Have to do the ack here because the ack triggers the release of messages from the server
         | 
| 143 | 
            -
                    # if you are using Client#qos prefetch and you will get extra messages sent through before
         | 
| 144 | 
            -
                    # the unsubscribe takes effect to stop messages being sent to this consumer unless the ack is
         | 
| 145 | 
            -
                    # deferred.
         | 
| 146 | 
            -
                    queue.ack() if @ack
         | 
| 147 | 
            -
                  end
         | 
| 148 | 
            -
                end
         | 
| 149 | 
            -
             | 
| 150 | 
            -
              end
         | 
| 151 | 
            -
             | 
| 152 | 
            -
            end
         | 
| @@ -1,305 +0,0 @@ | |
| 1 | 
            -
            # encoding: utf-8
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            if [].map.respond_to? :with_index
         | 
| 4 | 
            -
              class Array #:nodoc:
         | 
| 5 | 
            -
                def enum_with_index
         | 
| 6 | 
            -
                  each.with_index
         | 
| 7 | 
            -
                end
         | 
| 8 | 
            -
              end
         | 
| 9 | 
            -
            else
         | 
| 10 | 
            -
              require 'enumerator'
         | 
| 11 | 
            -
            end
         | 
| 12 | 
            -
             | 
| 13 | 
            -
            module Qrack
         | 
| 14 | 
            -
              module Transport #:nodoc: all
         | 
| 15 | 
            -
                class Buffer
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                  def initialize data = ''
         | 
| 18 | 
            -
                    @data = data
         | 
| 19 | 
            -
                    @pos = 0
         | 
| 20 | 
            -
                  end
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                  attr_reader :pos
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                  def data
         | 
| 25 | 
            -
                    @data.clone
         | 
| 26 | 
            -
                  end
         | 
| 27 | 
            -
                  alias :contents :data
         | 
| 28 | 
            -
                  alias :to_s :data
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                  def << data
         | 
| 31 | 
            -
                    @data << data.to_s
         | 
| 32 | 
            -
                    self
         | 
| 33 | 
            -
                  end
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                  def length
         | 
| 36 | 
            -
                    @data.bytesize
         | 
| 37 | 
            -
                  end
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                  def empty?
         | 
| 40 | 
            -
                    pos == length
         | 
| 41 | 
            -
                  end
         | 
| 42 | 
            -
             | 
| 43 | 
            -
                  def rewind
         | 
| 44 | 
            -
                    @pos = 0
         | 
| 45 | 
            -
                  end
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                  def read_properties *types
         | 
| 48 | 
            -
                    types.shift if types.first == :properties
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                    i = 0
         | 
| 51 | 
            -
                    values = []
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                    while props = read(:short)
         | 
| 54 | 
            -
                      (0..14).each do |n|
         | 
| 55 | 
            -
                        # no more property types
         | 
| 56 | 
            -
                        break unless types[i]
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                        # if flag is set
         | 
| 59 | 
            -
                        if props & (1<<(15-n)) != 0
         | 
| 60 | 
            -
                          if types[i] == :bit
         | 
| 61 | 
            -
                            # bit values exist in flags only
         | 
| 62 | 
            -
                            values << true
         | 
| 63 | 
            -
                          else
         | 
| 64 | 
            -
                            # save type name for later reading
         | 
| 65 | 
            -
                            values << types[i]
         | 
| 66 | 
            -
                          end
         | 
| 67 | 
            -
                        else
         | 
| 68 | 
            -
                          # property not set or is false bit
         | 
| 69 | 
            -
                          values << (types[i] == :bit ? false : nil)
         | 
| 70 | 
            -
                        end
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                        i+=1
         | 
| 73 | 
            -
                      end
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                      # bit(0) == 0 means no more property flags
         | 
| 76 | 
            -
                      break unless props & 1 == 1
         | 
| 77 | 
            -
                    end
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                    values.map do |value|
         | 
| 80 | 
            -
                      value.is_a?(Symbol) ? read(value) : value
         | 
| 81 | 
            -
                    end
         | 
| 82 | 
            -
                  end
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                  def read *types
         | 
| 85 | 
            -
                    if types.first == :properties
         | 
| 86 | 
            -
                      return read_properties(*types)
         | 
| 87 | 
            -
                    end
         | 
| 88 | 
            -
             | 
| 89 | 
            -
                    values = types.map do |type|
         | 
| 90 | 
            -
                      case type
         | 
| 91 | 
            -
                      when :octet
         | 
| 92 | 
            -
                        _read(1, 'C')
         | 
| 93 | 
            -
                      when :short
         | 
| 94 | 
            -
                        _read(2, 'n')
         | 
| 95 | 
            -
                      when :long
         | 
| 96 | 
            -
                        _read(4, 'N')
         | 
| 97 | 
            -
                      when :longlong
         | 
| 98 | 
            -
                        upper, lower = _read(8, 'NN')
         | 
| 99 | 
            -
                        upper << 32 | lower
         | 
| 100 | 
            -
                      when :shortstr
         | 
| 101 | 
            -
                        _read read(:octet)
         | 
| 102 | 
            -
                      when :longstr
         | 
| 103 | 
            -
                        _read read(:long)
         | 
| 104 | 
            -
                      when :timestamp
         | 
| 105 | 
            -
                        Time.at read(:longlong)
         | 
| 106 | 
            -
                      when :table
         | 
| 107 | 
            -
                        t = Hash.new
         | 
| 108 | 
            -
             | 
| 109 | 
            -
                        table = Buffer.new(read(:longstr))
         | 
| 110 | 
            -
                        until table.empty?
         | 
| 111 | 
            -
                          key, type = table.read(:shortstr, :octet)
         | 
| 112 | 
            -
                          key = key.intern
         | 
| 113 | 
            -
                          t[key] ||= case type
         | 
| 114 | 
            -
                                     when 83 # 'S'
         | 
| 115 | 
            -
                                       table.read(:longstr)
         | 
| 116 | 
            -
                                     when 73 # 'I'
         | 
| 117 | 
            -
                                       table.read(:long)
         | 
| 118 | 
            -
                                     when 68 # 'D'
         | 
| 119 | 
            -
                                       exp = table.read(:octet)
         | 
| 120 | 
            -
                                       num = table.read(:long)
         | 
| 121 | 
            -
                                       num / 10.0**exp
         | 
| 122 | 
            -
                                     when 84 # 'T'
         | 
| 123 | 
            -
                                       table.read(:timestamp)
         | 
| 124 | 
            -
                                     when 70 # 'F'
         | 
| 125 | 
            -
                                       table.read(:table)
         | 
| 126 | 
            -
                                     when 65 # 'A'
         | 
| 127 | 
            -
                                       table.read(:array)
         | 
| 128 | 
            -
                                     when 108 # 'l'
         | 
| 129 | 
            -
                                       table.read(:longlong)
         | 
| 130 | 
            -
                                     when 116 # 't'
         | 
| 131 | 
            -
                                       table.read(:octet)
         | 
| 132 | 
            -
                                     end
         | 
| 133 | 
            -
                        end
         | 
| 134 | 
            -
             | 
| 135 | 
            -
                        t
         | 
| 136 | 
            -
                      when :bit
         | 
| 137 | 
            -
                        if (@bits ||= []).empty?
         | 
| 138 | 
            -
                          val = read(:octet)
         | 
| 139 | 
            -
                          @bits = (0..7).map{|i| (val & (1 << i)) != 0 }
         | 
| 140 | 
            -
                        end
         | 
| 141 | 
            -
             | 
| 142 | 
            -
                        @bits.shift
         | 
| 143 | 
            -
                      when :array
         | 
| 144 | 
            -
                        a = Array.new
         | 
| 145 | 
            -
             | 
| 146 | 
            -
                        array = Buffer.new(read(:longstr))
         | 
| 147 | 
            -
                        until array.empty?
         | 
| 148 | 
            -
                          type = array.read(:octet)
         | 
| 149 | 
            -
                          a << case type
         | 
| 150 | 
            -
                               when 70 # 'F'
         | 
| 151 | 
            -
                                 array.read(:table)
         | 
| 152 | 
            -
                               end
         | 
| 153 | 
            -
                        end
         | 
| 154 | 
            -
             | 
| 155 | 
            -
                        a
         | 
| 156 | 
            -
                      else
         | 
| 157 | 
            -
                        raise Qrack::InvalidTypeError, "Cannot read data of type #{type}"
         | 
| 158 | 
            -
                      end
         | 
| 159 | 
            -
                    end
         | 
| 160 | 
            -
             | 
| 161 | 
            -
                    types.size == 1 ? values.first : values
         | 
| 162 | 
            -
                  end
         | 
| 163 | 
            -
             | 
| 164 | 
            -
                  def write type, data
         | 
| 165 | 
            -
                    case type
         | 
| 166 | 
            -
                    when :octet
         | 
| 167 | 
            -
                      _write(data, 'C')
         | 
| 168 | 
            -
                    when :short
         | 
| 169 | 
            -
                      _write(data, 'n')
         | 
| 170 | 
            -
                    when :long
         | 
| 171 | 
            -
                      _write(data, 'N')
         | 
| 172 | 
            -
                    when :longlong
         | 
| 173 | 
            -
                      lower =  data & 0xffffffff
         | 
| 174 | 
            -
                      upper = (data & ~0xffffffff) >> 32
         | 
| 175 | 
            -
                      _write([upper, lower], 'NN')
         | 
| 176 | 
            -
                    when :shortstr
         | 
| 177 | 
            -
                      data = (data || '').to_s
         | 
| 178 | 
            -
                      _write([data.bytesize, data], 'Ca*')
         | 
| 179 | 
            -
                    when :longstr
         | 
| 180 | 
            -
                      if data.is_a? Hash
         | 
| 181 | 
            -
                        write(:table, data)
         | 
| 182 | 
            -
                      else
         | 
| 183 | 
            -
                        data = (data || '').to_s
         | 
| 184 | 
            -
                        _write([data.bytesize, data], 'Na*')
         | 
| 185 | 
            -
                      end
         | 
| 186 | 
            -
                    when :timestamp
         | 
| 187 | 
            -
                      write(:longlong, data.to_i)
         | 
| 188 | 
            -
                    when :table
         | 
| 189 | 
            -
                      data ||= {}
         | 
| 190 | 
            -
                      write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
         | 
| 191 | 
            -
                                         table.write(:shortstr, key.to_s)
         | 
| 192 | 
            -
             | 
| 193 | 
            -
                                         case value
         | 
| 194 | 
            -
                                         when String
         | 
| 195 | 
            -
                                           table.write(:octet, 83) # 'S'
         | 
| 196 | 
            -
                                           table.write(:longstr, value.to_s)
         | 
| 197 | 
            -
                                         when Fixnum
         | 
| 198 | 
            -
                                           table.write(:octet, 73) # 'I'
         | 
| 199 | 
            -
                                           table.write(:long, value)
         | 
| 200 | 
            -
                                         when Float
         | 
| 201 | 
            -
                                           table.write(:octet, 68) # 'D'
         | 
| 202 | 
            -
                                           # XXX there's gotta be a better way to do this..
         | 
| 203 | 
            -
                                           exp = value.to_s.split('.').last.bytesize
         | 
| 204 | 
            -
                                           num = value * 10**exp
         | 
| 205 | 
            -
                                           table.write(:octet, exp)
         | 
| 206 | 
            -
                                           table.write(:long, num)
         | 
| 207 | 
            -
                                         when Time
         | 
| 208 | 
            -
                                           table.write(:octet, 84) # 'T'
         | 
| 209 | 
            -
                                           table.write(:timestamp, value)
         | 
| 210 | 
            -
                                         when Hash
         | 
| 211 | 
            -
                                           table.write(:octet, 70) # 'F'
         | 
| 212 | 
            -
                                           table.write(:table, value)
         | 
| 213 | 
            -
                                         end
         | 
| 214 | 
            -
             | 
| 215 | 
            -
                                         table
         | 
| 216 | 
            -
                                       end)
         | 
| 217 | 
            -
                    when :bit
         | 
| 218 | 
            -
                      [*data].to_enum(:each_slice, 8).each{|bits|
         | 
| 219 | 
            -
                        write(:octet, bits.enum_with_index.inject(0){ |byte, (bit, i)|
         | 
| 220 | 
            -
                                byte |= (1 << i) if bit
         | 
| 221 | 
            -
                                byte
         | 
| 222 | 
            -
                              })
         | 
| 223 | 
            -
                      }
         | 
| 224 | 
            -
                    when :properties
         | 
| 225 | 
            -
                      values = []
         | 
| 226 | 
            -
                      data.enum_with_index.inject(0) do |short, ((type, value), i)|
         | 
| 227 | 
            -
                        n = i % 15
         | 
| 228 | 
            -
                        last = i+1 == data.size
         | 
| 229 | 
            -
             | 
| 230 | 
            -
                        if (n == 0 and i != 0) or last
         | 
| 231 | 
            -
                          if data.size > i+1
         | 
| 232 | 
            -
                            short |= (1 << 0)
         | 
| 233 | 
            -
                          elsif last and value
         | 
| 234 | 
            -
                            values << [type,value]
         | 
| 235 | 
            -
                            short |= 1<<(15-n)
         | 
| 236 | 
            -
                          end
         | 
| 237 | 
            -
             | 
| 238 | 
            -
                          write(:short, short)
         | 
| 239 | 
            -
                          short = 0
         | 
| 240 | 
            -
                        end
         | 
| 241 | 
            -
             | 
| 242 | 
            -
                        if value and !last
         | 
| 243 | 
            -
                          values << [type,value]
         | 
| 244 | 
            -
                          short |= 1<<(15-n)
         | 
| 245 | 
            -
                        end
         | 
| 246 | 
            -
             | 
| 247 | 
            -
                        short
         | 
| 248 | 
            -
                      end
         | 
| 249 | 
            -
             | 
| 250 | 
            -
                      values.each do |type, value|
         | 
| 251 | 
            -
                        write(type, value) unless type == :bit
         | 
| 252 | 
            -
                      end
         | 
| 253 | 
            -
                    else
         | 
| 254 | 
            -
                      raise Qrack::InvalidTypeError, "Cannot write data of type #{type}"
         | 
| 255 | 
            -
                    end
         | 
| 256 | 
            -
             | 
| 257 | 
            -
                    self
         | 
| 258 | 
            -
                  end
         | 
| 259 | 
            -
             | 
| 260 | 
            -
                  def extract
         | 
| 261 | 
            -
                    begin
         | 
| 262 | 
            -
                      cur_data, cur_pos = @data.clone, @pos
         | 
| 263 | 
            -
                      yield self
         | 
| 264 | 
            -
                    rescue Qrack::BufferOverflowError
         | 
| 265 | 
            -
                      @data, @pos = cur_data, cur_pos
         | 
| 266 | 
            -
                      nil
         | 
| 267 | 
            -
                    end
         | 
| 268 | 
            -
                  end
         | 
| 269 | 
            -
             | 
| 270 | 
            -
                  def read_ready?(timeout, cancellator)
         | 
| 271 | 
            -
                    if @data.is_a?(Qrack::Client)
         | 
| 272 | 
            -
                      @data.read_ready?(timeout, cancellator)
         | 
| 273 | 
            -
                    else
         | 
| 274 | 
            -
                      true
         | 
| 275 | 
            -
                    end
         | 
| 276 | 
            -
                  end
         | 
| 277 | 
            -
             | 
| 278 | 
            -
                  def _read(size, pack = nil)
         | 
| 279 | 
            -
                    if @data.is_a?(Qrack::Client)
         | 
| 280 | 
            -
                      raw = @data.read(size)
         | 
| 281 | 
            -
                      return raw if raw.nil? or pack.nil?
         | 
| 282 | 
            -
                      return raw.unpack(pack).first
         | 
| 283 | 
            -
                    end
         | 
| 284 | 
            -
             | 
| 285 | 
            -
                    if @pos + size > length
         | 
| 286 | 
            -
                      raise Qrack::BufferOverflowError
         | 
| 287 | 
            -
                    else
         | 
| 288 | 
            -
                      data = @data[@pos,size]
         | 
| 289 | 
            -
                      @data[@pos,size] = ''
         | 
| 290 | 
            -
                      if pack
         | 
| 291 | 
            -
                        data = data.unpack(pack)
         | 
| 292 | 
            -
                        data = data.pop if data.size == 1
         | 
| 293 | 
            -
                      end
         | 
| 294 | 
            -
                      data
         | 
| 295 | 
            -
                    end
         | 
| 296 | 
            -
                  end
         | 
| 297 | 
            -
             | 
| 298 | 
            -
                  def _write data, pack = nil
         | 
| 299 | 
            -
                    data = [*data].pack(pack) if pack
         | 
| 300 | 
            -
                    @data[@pos,0] = data
         | 
| 301 | 
            -
                    @pos += data.bytesize
         | 
| 302 | 
            -
                  end
         | 
| 303 | 
            -
                end
         | 
| 304 | 
            -
              end
         | 
| 305 | 
            -
            end
         |