gorgon 0.5.0.rc1 → 0.6.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +2 -4
- data/gorgon.gemspec +0 -1
- data/lib/gorgon/amqp_service.rb +5 -5
- data/lib/gorgon/gem_command_handler.rb +2 -1
- data/lib/gorgon/listener.rb +7 -5
- data/lib/gorgon/originator_protocol.rb +1 -0
- data/lib/gorgon/version.rb +1 -1
- data/lib/gorgon/worker_manager.rb +5 -2
- data/lib/gorgon_amq-protocol/.gitignore +15 -0
- data/lib/gorgon_amq-protocol/.gitmodules +3 -0
- data/lib/gorgon_amq-protocol/.rspec +3 -0
- data/lib/gorgon_amq-protocol/.travis.yml +19 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/bit_set.rb +82 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/endianness.rb +15 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/int_allocator.rb +96 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/pack.rb +53 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol.rb +4 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/client.rb +2322 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/constants.rb +22 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/exceptions.rb +60 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/float_32bit.rb +14 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/frame.rb +210 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table.rb +142 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table_value_decoder.rb +190 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table_value_encoder.rb +123 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/type_constants.rb +26 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/version.rb +5 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/settings.rb +114 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/uri.rb +37 -0
- data/lib/gorgon_bunny/lib/gorgon_amq/protocol/extensions.rb +16 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny.rb +89 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/authentication/credentials_encoder.rb +55 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/authentication/external_mechanism_encoder.rb +27 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/authentication/plain_mechanism_encoder.rb +19 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/channel.rb +1875 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/channel_id_allocator.rb +80 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/compatibility.rb +24 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/atomic_fixnum.rb +74 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/condition.rb +66 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/continuation_queue.rb +41 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/linked_continuation_queue.rb +61 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/synchronized_sorted_set.rb +56 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/consumer.rb +123 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/consumer_tag_generator.rb +23 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/consumer_work_pool.rb +94 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/delivery_info.rb +93 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/exceptions.rb +236 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/exchange.rb +271 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/framing.rb +56 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/heartbeat_sender.rb +70 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/message_properties.rb +119 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/queue.rb +387 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/reader_loop.rb +116 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/return_info.rb +74 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/session.rb +1044 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/socket.rb +83 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/ssl_socket.rb +57 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/system_timer.rb +20 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/test_kit.rb +27 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/timeout.rb +18 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/transport.rb +398 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/version.rb +6 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/versioned_delivery_tag.rb +28 -0
- data/spec/crash_reporter_spec.rb +1 -1
- data/spec/gem_command_handler_spec.rb +2 -2
- data/spec/listener_spec.rb +5 -5
- data/spec/worker_manager_spec.rb +3 -3
- metadata +56 -17
@@ -0,0 +1,22 @@
|
|
1
|
+
module GorgonAMQ
|
2
|
+
module Protocol
|
3
|
+
TLS_PORT = 5671
|
4
|
+
SSL_PORT = 5671
|
5
|
+
|
6
|
+
# caching
|
7
|
+
EMPTY_STRING = "".freeze
|
8
|
+
|
9
|
+
PACK_INT8 = 'c'.freeze
|
10
|
+
PACK_CHAR = 'C'.freeze
|
11
|
+
PACK_UINT16 = 'n'.freeze
|
12
|
+
PACK_UINT16_X2 = 'n2'.freeze
|
13
|
+
PACK_UINT32 = 'N'.freeze
|
14
|
+
PACK_UINT32_X2 = 'N2'.freeze
|
15
|
+
PACK_INT64 = 'q'.freeze
|
16
|
+
PACK_UCHAR_UINT32 = 'CN'.freeze
|
17
|
+
PACK_CHAR_UINT16_UINT32 = 'cnN'.freeze
|
18
|
+
|
19
|
+
PACK_32BIT_FLOAT = 'f'.freeze
|
20
|
+
PACK_64BIT_FLOAT = 'G'.freeze
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module GorgonAMQ
|
2
|
+
module Protocol
|
3
|
+
class Error < StandardError
|
4
|
+
DEFAULT_MESSAGE = "AMQP error".freeze
|
5
|
+
|
6
|
+
def self.inherited(subclass)
|
7
|
+
@_subclasses ||= []
|
8
|
+
@_subclasses << subclass
|
9
|
+
end # self.inherited(subclazz)
|
10
|
+
|
11
|
+
def self.subclasses_with_values
|
12
|
+
@_subclasses.select{ |k| defined?(k::VALUE) }
|
13
|
+
end # self.subclasses_with_values
|
14
|
+
|
15
|
+
def self.[](code)
|
16
|
+
if result = subclasses_with_values.detect { |klass| klass::VALUE == code }
|
17
|
+
result
|
18
|
+
else
|
19
|
+
raise "No such exception class for code #{code}" unless result
|
20
|
+
end # if
|
21
|
+
end # self.[]
|
22
|
+
|
23
|
+
def initialize(message = self.class::DEFAULT_MESSAGE)
|
24
|
+
super(message)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class FrameTypeError < Protocol::Error
|
29
|
+
def initialize(types)
|
30
|
+
super("Must be one of #{types.inspect}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class EmptyResponseError < Protocol::Error
|
35
|
+
DEFAULT_MESSAGE = "Empty response received from the server."
|
36
|
+
|
37
|
+
def initialize(message = self.class::DEFAULT_MESSAGE)
|
38
|
+
super(message)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class BadResponseError < Protocol::Error
|
43
|
+
def initialize(argument, expected, actual)
|
44
|
+
super("Argument #{argument} has to be #{expected.inspect}, was #{data.inspect}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class SoftError < Protocol::Error
|
49
|
+
def self.inherited(subclass)
|
50
|
+
Error.inherited(subclass)
|
51
|
+
end # self.inherited(subclass)
|
52
|
+
end
|
53
|
+
|
54
|
+
class HardError < Protocol::Error
|
55
|
+
def self.inherited(subclass)
|
56
|
+
Error.inherited(subclass)
|
57
|
+
end # self.inherited(subclass)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module GorgonAMQ
|
2
|
+
module Protocol
|
3
|
+
# Allows distinguishing between 32-bit and 64-bit floats in Ruby.
|
4
|
+
# Useful in cases when RabbitMQ plugins encode
|
5
|
+
# values as 32 bit numbers.
|
6
|
+
class Float32Bit
|
7
|
+
attr_reader :value
|
8
|
+
|
9
|
+
def initialize(value)
|
10
|
+
@value = value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
|
3
|
+
module GorgonAMQ
|
4
|
+
module Protocol
|
5
|
+
class Frame
|
6
|
+
TYPES = {:method => 1, :headers => 2, :body => 3, :heartbeat => 8}.freeze
|
7
|
+
TYPES_REVERSE = TYPES.invert.freeze
|
8
|
+
TYPES_OPTIONS = TYPES.keys.freeze
|
9
|
+
CHANNEL_RANGE = (0..65535).freeze
|
10
|
+
FINAL_OCTET = "\xCE".freeze # 206
|
11
|
+
|
12
|
+
def self.encoded_payload(payload)
|
13
|
+
if payload.respond_to?(:force_encoding) && payload.encoding.name != 'BINARY'
|
14
|
+
# Only copy if we have to.
|
15
|
+
payload = payload.dup.force_encoding('BINARY')
|
16
|
+
end
|
17
|
+
payload
|
18
|
+
end
|
19
|
+
|
20
|
+
# The channel number is 0 for all frames which are global to the connection and 1-65535 for frames that refer to specific channels.
|
21
|
+
def self.encode_to_array(type, payload, channel)
|
22
|
+
raise RuntimeError.new("Channel has to be 0 or an integer in range 1..65535 but was #{channel.inspect}") unless CHANNEL_RANGE.include?(channel)
|
23
|
+
raise RuntimeError.new("Payload can't be nil") if payload.nil?
|
24
|
+
components = []
|
25
|
+
components << [find_type(type), channel, payload.bytesize].pack(PACK_CHAR_UINT16_UINT32)
|
26
|
+
components << encoded_payload(payload)
|
27
|
+
components << FINAL_OCTET
|
28
|
+
components
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.encode(type, payload, channel)
|
32
|
+
encode_to_array(type, payload, channel).join
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
alias_method :__new__, :new unless method_defined?(:__new__) # because of reloading
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.new(original_type, *args)
|
40
|
+
type_id = find_type(original_type)
|
41
|
+
klass = CLASSES[type_id]
|
42
|
+
klass.new(*args)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.find_type(type)
|
46
|
+
type_id = if Symbol === type then TYPES[type] else type end
|
47
|
+
raise FrameTypeError.new(TYPES_OPTIONS) if type == nil || !TYPES_REVERSE.has_key?(type_id)
|
48
|
+
type_id
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.decode(*)
|
52
|
+
raise NotImplementedError.new <<-EOF
|
53
|
+
You are supposed to redefine this method, because it's dependent on used IO adapter.
|
54
|
+
|
55
|
+
This functionality is part of the https://github.com/ruby-amqp/amq-client library.
|
56
|
+
EOF
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.decode_header(header)
|
60
|
+
raise EmptyResponseError if header == nil || header.empty?
|
61
|
+
|
62
|
+
type_id, channel, size = header.unpack(PACK_CHAR_UINT16_UINT32)
|
63
|
+
type = TYPES_REVERSE[type_id]
|
64
|
+
raise FrameTypeError.new(TYPES_OPTIONS) unless type
|
65
|
+
[type, channel, size]
|
66
|
+
end
|
67
|
+
|
68
|
+
def final?
|
69
|
+
true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class FrameSubclass < Frame
|
74
|
+
# Restore original new
|
75
|
+
class << self
|
76
|
+
alias_method :new, :__new__
|
77
|
+
undef_method :decode if method_defined?(:decode)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.id
|
81
|
+
@id
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.encode(payload, channel)
|
85
|
+
super(@id, payload, channel)
|
86
|
+
end
|
87
|
+
|
88
|
+
attr_accessor :channel
|
89
|
+
attr_reader :payload
|
90
|
+
def initialize(payload, channel)
|
91
|
+
@payload, @channel = payload, channel
|
92
|
+
end
|
93
|
+
|
94
|
+
def size
|
95
|
+
@payload.bytesize
|
96
|
+
end
|
97
|
+
|
98
|
+
# TODO: remove once we are sure none of the clients
|
99
|
+
# uses this method directly
|
100
|
+
# @api private
|
101
|
+
def encode_to_array
|
102
|
+
components = []
|
103
|
+
components << [self.class.id, @channel, @payload.bytesize].pack(PACK_CHAR_UINT16_UINT32)
|
104
|
+
components << self.class.encoded_payload(@payload)
|
105
|
+
components << FINAL_OCTET
|
106
|
+
components
|
107
|
+
end
|
108
|
+
|
109
|
+
def encode
|
110
|
+
s = [self.class.id, @channel, @payload.bytesize].pack(PACK_CHAR_UINT16_UINT32)
|
111
|
+
s << self.class.encoded_payload(@payload)
|
112
|
+
s << FINAL_OCTET
|
113
|
+
s
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class MethodFrame < FrameSubclass
|
118
|
+
@id = 1
|
119
|
+
|
120
|
+
def method_class
|
121
|
+
@method_class ||= begin
|
122
|
+
klass_id, method_id = self.payload.unpack(PACK_UINT16_X2)
|
123
|
+
index = klass_id << 16 | method_id
|
124
|
+
GorgonAMQ::Protocol::METHODS[index]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def final?
|
129
|
+
!self.method_class.has_content?
|
130
|
+
end # final?
|
131
|
+
|
132
|
+
def decode_payload
|
133
|
+
self.method_class.decode(@payload[4..-1])
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class HeaderFrame < FrameSubclass
|
138
|
+
@id = 2
|
139
|
+
|
140
|
+
def final?
|
141
|
+
false
|
142
|
+
end
|
143
|
+
|
144
|
+
def body_size
|
145
|
+
decode_payload
|
146
|
+
@body_size
|
147
|
+
end
|
148
|
+
|
149
|
+
def weight
|
150
|
+
decode_payload
|
151
|
+
@weight
|
152
|
+
end
|
153
|
+
|
154
|
+
def klass_id
|
155
|
+
decode_payload
|
156
|
+
@klass_id
|
157
|
+
end
|
158
|
+
|
159
|
+
def properties
|
160
|
+
decode_payload
|
161
|
+
@properties
|
162
|
+
end
|
163
|
+
|
164
|
+
def decode_payload
|
165
|
+
@decoded_payload ||= begin
|
166
|
+
@klass_id, @weight = @payload.unpack(PACK_UINT16_X2)
|
167
|
+
# the total size of the content body, that is, the sum of the body sizes for the
|
168
|
+
# following content body frames. Zero indicates that there are no content body frames.
|
169
|
+
# So this is NOT related to this very header frame!
|
170
|
+
@body_size = GorgonAMQ::Hacks.unpack_uint64_big_endian(@payload[4..11]).first
|
171
|
+
@data = @payload[12..-1]
|
172
|
+
@properties = Basic.decode_properties(@data)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class BodyFrame < FrameSubclass
|
178
|
+
@id = 3
|
179
|
+
|
180
|
+
def decode_payload
|
181
|
+
@payload
|
182
|
+
end
|
183
|
+
|
184
|
+
def final?
|
185
|
+
# we cannot know whether it is final or not so framing code in amq-client
|
186
|
+
# checks this over the entire frameset. MK.
|
187
|
+
false
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class HeartbeatFrame < FrameSubclass
|
192
|
+
@id = 8
|
193
|
+
|
194
|
+
def final?
|
195
|
+
true
|
196
|
+
end # final?
|
197
|
+
|
198
|
+
def self.encode
|
199
|
+
super(Protocol::EMPTY_STRING, 0)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
Frame::CLASSES = {
|
204
|
+
Frame::TYPES[:method] => MethodFrame,
|
205
|
+
Frame::TYPES[:headers] => HeaderFrame,
|
206
|
+
Frame::TYPES[:body] => BodyFrame,
|
207
|
+
Frame::TYPES[:heartbeat] => HeartbeatFrame
|
208
|
+
}
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
|
3
|
+
require "gorgon_amq/protocol/client"
|
4
|
+
require "gorgon_amq/protocol/type_constants"
|
5
|
+
require "gorgon_amq/protocol/table_value_encoder"
|
6
|
+
require "gorgon_amq/protocol/table_value_decoder"
|
7
|
+
|
8
|
+
module GorgonAMQ
|
9
|
+
module Protocol
|
10
|
+
class Table
|
11
|
+
|
12
|
+
#
|
13
|
+
# Behaviors
|
14
|
+
#
|
15
|
+
|
16
|
+
include TypeConstants
|
17
|
+
|
18
|
+
|
19
|
+
#
|
20
|
+
# API
|
21
|
+
#
|
22
|
+
|
23
|
+
class InvalidTableError < StandardError
|
24
|
+
def initialize(key, value)
|
25
|
+
super("Invalid table value on key #{key}: #{value.inspect} (#{value.class})")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def self.encode(table)
|
31
|
+
buffer = String.new
|
32
|
+
|
33
|
+
table ||= {}
|
34
|
+
|
35
|
+
table.each do |key, value|
|
36
|
+
key = key.to_s # it can be a symbol as well
|
37
|
+
buffer << key.bytesize.chr + key
|
38
|
+
|
39
|
+
case value
|
40
|
+
when Hash then
|
41
|
+
buffer << TYPE_HASH
|
42
|
+
buffer << self.encode(value)
|
43
|
+
else
|
44
|
+
buffer << TableValueEncoder.encode(value)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
[buffer.bytesize].pack(PACK_UINT32) + buffer
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
def self.decode(data)
|
55
|
+
table = Hash.new
|
56
|
+
table_length = data.unpack(PACK_UINT32).first
|
57
|
+
|
58
|
+
return table if table_length.zero?
|
59
|
+
|
60
|
+
offset = 4
|
61
|
+
while offset <= table_length
|
62
|
+
key, offset = decode_table_key(data, offset)
|
63
|
+
type, offset = TableValueDecoder.decode_value_type(data, offset)
|
64
|
+
|
65
|
+
table[key] = case type
|
66
|
+
when TYPE_STRING
|
67
|
+
v, offset = TableValueDecoder.decode_string(data, offset)
|
68
|
+
v
|
69
|
+
when TYPE_INTEGER
|
70
|
+
v, offset = TableValueDecoder.decode_integer(data, offset)
|
71
|
+
v
|
72
|
+
when TYPE_DECIMAL
|
73
|
+
v, offset = TableValueDecoder.decode_big_decimal(data, offset)
|
74
|
+
v
|
75
|
+
when TYPE_TIME
|
76
|
+
v, offset = TableValueDecoder.decode_time(data, offset)
|
77
|
+
v
|
78
|
+
when TYPE_HASH
|
79
|
+
v, offset = TableValueDecoder.decode_hash(data, offset)
|
80
|
+
v
|
81
|
+
when TYPE_BOOLEAN
|
82
|
+
v, offset = TableValueDecoder.decode_boolean(data, offset)
|
83
|
+
v
|
84
|
+
when TYPE_SIGNED_8BIT then
|
85
|
+
v, offset = TableValueDecoder.decode_short_short(data, offset)
|
86
|
+
v
|
87
|
+
when TYPE_SIGNED_16BIT then
|
88
|
+
v, offset = TableValueDecoder.decode_short(data, offset)
|
89
|
+
v
|
90
|
+
when TYPE_SIGNED_64BIT then
|
91
|
+
v, offset = TableValueDecoder.decode_long(data, offset)
|
92
|
+
v
|
93
|
+
when TYPE_32BIT_FLOAT then
|
94
|
+
v, offset = TableValueDecoder.decode_32bit_float(data, offset)
|
95
|
+
v
|
96
|
+
when TYPE_64BIT_FLOAT then
|
97
|
+
v, offset = TableValueDecoder.decode_64bit_float(data, offset)
|
98
|
+
v
|
99
|
+
when TYPE_VOID
|
100
|
+
nil
|
101
|
+
when TYPE_ARRAY
|
102
|
+
v, offset = TableValueDecoder.decode_array(data, offset)
|
103
|
+
v
|
104
|
+
else
|
105
|
+
raise ArgumentError, "Not a valid type: #{type.inspect}\nData: #{data.inspect}\nUnprocessed data: #{data[offset..-1].inspect}\nOffset: #{offset}\nTotal size: #{table_length}\nProcessed data: #{table.inspect}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
table
|
110
|
+
end # self.decode
|
111
|
+
|
112
|
+
|
113
|
+
def self.length(data)
|
114
|
+
data.unpack(PACK_UINT32).first
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def self.hash_size(value)
|
119
|
+
acc = 0
|
120
|
+
value.each do |k, v|
|
121
|
+
acc += (1 + k.to_s.bytesize)
|
122
|
+
acc += TableValueEncoder.field_value_size(v)
|
123
|
+
end
|
124
|
+
|
125
|
+
acc
|
126
|
+
end # self.hash_size(value)
|
127
|
+
|
128
|
+
|
129
|
+
def self.decode_table_key(data, offset)
|
130
|
+
key_length = data.slice(offset, 1).unpack(PACK_CHAR).first
|
131
|
+
offset += 1
|
132
|
+
key = data.slice(offset, key_length)
|
133
|
+
offset += key_length
|
134
|
+
|
135
|
+
[key, offset]
|
136
|
+
end # self.decode_table_key(data, offset)
|
137
|
+
|
138
|
+
|
139
|
+
|
140
|
+
end # Table
|
141
|
+
end # Protocol
|
142
|
+
end # AMQ
|