codeclimate-poseidon 0.0.8

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.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +14 -0
  5. data/.yardopts +8 -0
  6. data/CHANGES.md +31 -0
  7. data/Gemfile +13 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +72 -0
  10. data/Rakefile +20 -0
  11. data/TODO.md +27 -0
  12. data/examples/consumer.rb +18 -0
  13. data/examples/producer.rb +9 -0
  14. data/lib/poseidon.rb +120 -0
  15. data/lib/poseidon/broker_pool.rb +86 -0
  16. data/lib/poseidon/cluster_metadata.rb +94 -0
  17. data/lib/poseidon/compressed_value.rb +23 -0
  18. data/lib/poseidon/compression.rb +30 -0
  19. data/lib/poseidon/compression/gzip_codec.rb +23 -0
  20. data/lib/poseidon/compression/snappy_codec.rb +29 -0
  21. data/lib/poseidon/connection.rb +169 -0
  22. data/lib/poseidon/fetched_message.rb +37 -0
  23. data/lib/poseidon/message.rb +151 -0
  24. data/lib/poseidon/message_conductor.rb +86 -0
  25. data/lib/poseidon/message_set.rb +80 -0
  26. data/lib/poseidon/message_to_send.rb +33 -0
  27. data/lib/poseidon/messages_for_broker.rb +56 -0
  28. data/lib/poseidon/messages_to_send.rb +47 -0
  29. data/lib/poseidon/messages_to_send_batch.rb +27 -0
  30. data/lib/poseidon/partition_consumer.rb +225 -0
  31. data/lib/poseidon/producer.rb +199 -0
  32. data/lib/poseidon/producer_compression_config.rb +37 -0
  33. data/lib/poseidon/protocol.rb +122 -0
  34. data/lib/poseidon/protocol/protocol_struct.rb +256 -0
  35. data/lib/poseidon/protocol/request_buffer.rb +77 -0
  36. data/lib/poseidon/protocol/response_buffer.rb +72 -0
  37. data/lib/poseidon/sync_producer.rb +161 -0
  38. data/lib/poseidon/topic_metadata.rb +89 -0
  39. data/lib/poseidon/version.rb +4 -0
  40. data/log/.gitkeep +0 -0
  41. data/poseidon.gemspec +27 -0
  42. data/spec/integration/multiple_brokers/consumer_spec.rb +45 -0
  43. data/spec/integration/multiple_brokers/metadata_failures_spec.rb +144 -0
  44. data/spec/integration/multiple_brokers/rebalance_spec.rb +69 -0
  45. data/spec/integration/multiple_brokers/round_robin_spec.rb +41 -0
  46. data/spec/integration/multiple_brokers/spec_helper.rb +60 -0
  47. data/spec/integration/simple/compression_spec.rb +23 -0
  48. data/spec/integration/simple/connection_spec.rb +35 -0
  49. data/spec/integration/simple/multiple_brokers_spec.rb +10 -0
  50. data/spec/integration/simple/simple_producer_and_consumer_spec.rb +121 -0
  51. data/spec/integration/simple/spec_helper.rb +16 -0
  52. data/spec/integration/simple/truncated_messages_spec.rb +46 -0
  53. data/spec/integration/simple/unavailable_broker_spec.rb +72 -0
  54. data/spec/spec_helper.rb +32 -0
  55. data/spec/test_cluster.rb +211 -0
  56. data/spec/unit/broker_pool_spec.rb +98 -0
  57. data/spec/unit/cluster_metadata_spec.rb +46 -0
  58. data/spec/unit/compression/gzip_codec_spec.rb +34 -0
  59. data/spec/unit/compression/snappy_codec_spec.rb +49 -0
  60. data/spec/unit/compression_spec.rb +17 -0
  61. data/spec/unit/connection_spec.rb +4 -0
  62. data/spec/unit/fetched_message_spec.rb +11 -0
  63. data/spec/unit/message_conductor_spec.rb +164 -0
  64. data/spec/unit/message_set_spec.rb +42 -0
  65. data/spec/unit/message_spec.rb +129 -0
  66. data/spec/unit/message_to_send_spec.rb +10 -0
  67. data/spec/unit/messages_for_broker_spec.rb +54 -0
  68. data/spec/unit/messages_to_send_batch_spec.rb +25 -0
  69. data/spec/unit/messages_to_send_spec.rb +63 -0
  70. data/spec/unit/partition_consumer_spec.rb +142 -0
  71. data/spec/unit/producer_compression_config_spec.rb +42 -0
  72. data/spec/unit/producer_spec.rb +51 -0
  73. data/spec/unit/protocol/request_buffer_spec.rb +16 -0
  74. data/spec/unit/protocol_spec.rb +54 -0
  75. data/spec/unit/sync_producer_spec.rb +156 -0
  76. data/spec/unit/topic_metadata_spec.rb +43 -0
  77. metadata +225 -0
@@ -0,0 +1,199 @@
1
+ module Poseidon
2
+ # Provides a high level interface for sending messages to a cluster
3
+ # of Kafka brokers.
4
+ #
5
+ # ## Producer Creation
6
+ #
7
+ # Producer requires a broker list and a client_id:
8
+ #
9
+ # producer = Producer.new(["broker1:port1", "broker2:port1"], "my_client_id",
10
+ # :type => :sync)
11
+ #
12
+ # The broker list is only used to bootstrap our knowledge of the cluster --
13
+ # it does not need to contain every broker. The client id should be unique
14
+ # across all clients in the cluster.
15
+ #
16
+ # ## Sending Messages
17
+ #
18
+ # Messages must have a topic before being sent:
19
+ #
20
+ # messages = []
21
+ # messages << Poseidon::MessageToSend.new("topic1", "Hello Word")
22
+ # messages << Poseidon::MessageToSend.new("user_updates_topic", user.update, user.id)
23
+ # producer.send_messages(messages)
24
+ #
25
+ # ## Producer Types
26
+ #
27
+ # There are two types of producers: sync and async. They can be specified
28
+ # via the :type option when creating a producer.
29
+ #
30
+ # ## Sync Producer
31
+ #
32
+ # The :sync producer blocks while sends messages to the cluster. The more
33
+ # messages you can send per #send_messages call the more efficient it will
34
+ # be.
35
+ #
36
+ # ## Compression
37
+ #
38
+ # When creating the producer you can specify a compression method:
39
+ #
40
+ # producer = Producer.new(["broker1:port1"], "my_client_id",
41
+ # :type => :sync, :compression_codec => :gzip)
42
+ #
43
+ # If you don't specify which topics to compress it will compress all topics.
44
+ # You can specify a set of topics to compress when creating the producer:
45
+ #
46
+ # producer = Producer.new(["broker1:port1"], "my_client_id",
47
+ # :type => :sync, :compression_codec => :gzip,
48
+ # :compressed_topics => ["compressed_topic_1"])
49
+ #
50
+ # ## Partitioning
51
+ #
52
+ # For keyless messages the producer will round-robin messages to all
53
+ # _available_ partitions for at topic. This means that if we are unable to
54
+ # send messages to a specific broker we'll retry sending those to a different
55
+ # broker.
56
+ #
57
+ # However, if you specify a key when creating the message, the producer
58
+ # will choose a partition based on the key and only send to that partition.
59
+ #
60
+ # ## Custom Partitioning
61
+ #
62
+ # You may also specify a custom partitioning scheme for messages by passing
63
+ # a Proc (or any object that responds to #call) to the Producer. The proc
64
+ # must return a Fixnum >= 0 and less-than partition_count.
65
+ #
66
+ # my_partitioner = Proc.new { |key, partition_count| Zlib::crc32(key) % partition_count }
67
+ #
68
+ # producer = Producer.new(["broker1:port1", "broker2:port1"], "my_client_id",
69
+ # :type => :sync, :partitioner => my_partitioner)
70
+ #
71
+ # @api public
72
+ class Producer
73
+ # @api private
74
+ VALID_OPTIONS = [
75
+ :ack_timeout_ms,
76
+ :compressed_topics,
77
+ :compression_codec,
78
+ :max_send_retries,
79
+ :metadata_refresh_interval_ms,
80
+ :partitioner,
81
+ :retry_backoff_ms,
82
+ :required_acks,
83
+ :socket_timeout_ms,
84
+ :type,
85
+ ]
86
+
87
+ # @api private
88
+ OPTION_DEFAULTS = {
89
+ :type => :sync
90
+ }
91
+
92
+ # Returns a new Producer.
93
+ #
94
+ # @param [Array<String>] brokers An array of brokers in the form "host1:port1"
95
+ #
96
+ # @param [String] client_id A client_id used to indentify the producer.
97
+ #
98
+ # @param [Hash] options
99
+ #
100
+ # @option options [:sync / :async] :type (:sync)
101
+ # Whether we should send messages right away or queue them and send
102
+ # them in the background.
103
+ #
104
+ # @option options [:gzip / :snappy / :none] :compression_codec (:none)
105
+ # Type of compression to use.
106
+ #
107
+ # @option options [Enumberable<String>] :compressed_topics (nil)
108
+ # Topics to compress. If this is not specified we will compress all
109
+ # topics provided that +:compression_codec+ is set.
110
+ #
111
+ # @option options [Integer: Milliseconds] :metadata_refresh_interval_ms (600_000)
112
+ # How frequently we should update the topic metadata in milliseconds.
113
+ #
114
+ # @option options [#call, nil] :partitioner
115
+ # Object which partitions messages based on key.
116
+ # Responds to #call(key, partition_count).
117
+ #
118
+ # @option options [Integer] :max_send_retries (3)
119
+ # Number of times to retry sending of messages to a leader.
120
+ #
121
+ # @option options [Integer] :retry_backoff_ms (100)
122
+ # The amount of time (in milliseconds) to wait before refreshing the metadata
123
+ # after we are unable to send messages.
124
+ # Number of times to retry sending of messages to a leader.
125
+ #
126
+ # @option options [Integer] :required_acks (0)
127
+ # The number of acks required per request.
128
+ #
129
+ # @option options [Integer] :ack_timeout_ms (1500)
130
+ # How long the producer waits for acks.
131
+ #
132
+ # @option options [Integer] :socket_timeout_ms] (10000)
133
+ # How long the producer socket waits for any reply from server.
134
+ #
135
+ # @api public
136
+ def initialize(brokers, client_id, options = {})
137
+ options = options.dup
138
+ validate_options(options)
139
+
140
+ if !brokers.respond_to?(:each)
141
+ raise ArgumentError, "brokers must respond to #each"
142
+ end
143
+ @brokers = brokers
144
+ @client_id = client_id
145
+ @producer = build_producer(options)
146
+ @shutdown = false
147
+ end
148
+
149
+ # Send messages to the cluster. Raises an exception if the producer fails to send the messages.
150
+ #
151
+ # @param [Enumerable<MessageToSend>] messages
152
+ # Messages must have a +topic+ set and may have a +key+ set.
153
+ #
154
+ # @return [Boolean]
155
+ #
156
+ # @api public
157
+ def send_messages(messages)
158
+ raise Errors::ProducerShutdownError if @shutdown
159
+ if !messages.respond_to?(:each)
160
+ raise ArgumentError, "messages must respond to #each"
161
+ end
162
+
163
+ @producer.send_messages(convert_to_messages_objects(messages))
164
+ end
165
+
166
+ # Closes all open connections to brokers
167
+ def close
168
+ @shutdown = true
169
+ @producer.close
170
+ end
171
+
172
+ alias_method :shutdown, :close
173
+
174
+ private
175
+ def validate_options(options)
176
+ unknown_keys = options.keys - VALID_OPTIONS
177
+ if unknown_keys.any?
178
+ raise ArgumentError, "Unknown options: #{unknown_keys.inspect}"
179
+ end
180
+
181
+ @type = options.delete(:type) || :sync
182
+ end
183
+
184
+ def convert_to_messages_objects(messages)
185
+ messages.map do |m|
186
+ Message.new(:value => m.value, :topic => m.topic, :key => m.key)
187
+ end
188
+ end
189
+
190
+ def build_producer(options)
191
+ case @type
192
+ when :sync
193
+ SyncProducer.new(@client_id, @brokers, options)
194
+ when :async
195
+ raise "Not implemented yet"
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,37 @@
1
+ module Poseidon
2
+ # @api private
3
+ class ProducerCompressionConfig
4
+ COMPRESSION_CODEC_MAP = {
5
+ :gzip => Compression::GzipCodec,
6
+ :snappy => Compression::SnappyCodec,
7
+ :none => nil
8
+ }
9
+
10
+ def initialize(compression_codec, compressed_topics)
11
+ if compression_codec
12
+ unless COMPRESSION_CODEC_MAP.has_key?(compression_codec)
13
+ raise ArgumentError, "Unknown compression codec: '#{compression_codec}' (accepted: #{COMPRESSION_CODEC_MAP.keys.inspect})"
14
+ end
15
+ @compression_codec = COMPRESSION_CODEC_MAP[compression_codec]
16
+ else
17
+ @compression_codec = nil
18
+ end
19
+
20
+ if compressed_topics
21
+ @compressed_topics = Set.new(compressed_topics)
22
+ else
23
+ @compressed_topics = nil
24
+ end
25
+ end
26
+
27
+ def compression_codec_for_topic(topic)
28
+ return false if @compression_codec.nil?
29
+
30
+ if @compressed_topics.nil? || (@compressed_topics && @compressed_topics.include?(topic))
31
+ @compression_codec
32
+ else
33
+ false
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,122 @@
1
+ module Poseidon
2
+ # @api private
3
+ module Protocol
4
+ require "poseidon/protocol/protocol_struct"
5
+ require "poseidon/protocol/request_buffer"
6
+ require "poseidon/protocol/response_buffer"
7
+
8
+ API_KEYS = {
9
+ :produce => 0,
10
+ :fetch => 1,
11
+ :offset => 2,
12
+ :metadata => 3
13
+ }
14
+
15
+ # Request/Response Common Structures
16
+ RequestCommon = ProtocolStruct.new(:api_key => :int16,
17
+ :api_version => :int16,
18
+ :correlation_id => :int32,
19
+ :client_id => :string)
20
+ ResponseCommon = ProtocolStruct.new(:correlation_id => :int32)
21
+
22
+ # MessageSet Common Structure
23
+ MessageStruct = ProtocolStruct.new(:magic_type => :int8,
24
+ :attributes => :int8,
25
+ :key => :bytes,
26
+ :value => :bytes).prepend_size.prepend_crc32.truncatable
27
+ MessageWithOffsetStruct = ProtocolStruct.new(:offset => :int64,
28
+ :message => MessageStruct)
29
+
30
+ # When part of produce requests of fetch responses a MessageSet
31
+ # has a prepended size. When a MessageSet is compressed and
32
+ # nested in a Message size is not prepended.
33
+ MessageSetStruct = ProtocolStruct.new(:messages => [Message]).
34
+ size_bound_array(:messages)
35
+ MessageSetStructWithSize = MessageSetStruct.dup.prepend_size
36
+
37
+ # Produce Request
38
+ MessagesForPartition = ProtocolStruct.new(:partition => :int32,
39
+ :message_set => MessageSet)
40
+ MessagesForTopic = ProtocolStruct.new(:topic => :string,
41
+ :messages_for_partitions =>
42
+ [MessagesForPartition])
43
+ ProduceRequest = ProtocolStruct.new(:common => RequestCommon,
44
+ :required_acks => :int16,
45
+ :timeout => :int32,
46
+ :messages_for_topics => [MessagesForTopic])
47
+
48
+ # Produce Response
49
+ ProducePartitionResponse = ProtocolStruct.new(:partition => :int32,
50
+ :error => :int16,
51
+ :offset => :int64)
52
+ ProduceTopicResponse = ProtocolStruct.new(:topic => :string,
53
+ :partitions => [ProducePartitionResponse])
54
+ ProduceResponse = ProtocolStruct.new(:common => ResponseCommon,
55
+ :topic_response => [ProduceTopicResponse])
56
+
57
+ # Fetch Request
58
+ PartitionFetch = ProtocolStruct.new(:partition => :int32,
59
+ :fetch_offset => :int64,
60
+ :max_bytes => :int32)
61
+ TopicFetch = ProtocolStruct.new(:topic => :string,
62
+ :partition_fetches => [PartitionFetch])
63
+ FetchRequest = ProtocolStruct.new(:common => RequestCommon,
64
+ :replica_id => :int32,
65
+ :max_wait_time => :int32,
66
+ :min_bytes => :int32,
67
+ :topic_fetches => [TopicFetch])
68
+
69
+ # Fetch Response
70
+ PartitionFetchResponse = ProtocolStruct.new(:partition => :int32,
71
+ :error => :int16,
72
+ :highwater_mark_offset => :int64,
73
+ :message_set => MessageSet)
74
+ TopicFetchResponse = ProtocolStruct.new(:topic => :string,
75
+ :partition_fetch_responses => [PartitionFetchResponse])
76
+ FetchResponse = ProtocolStruct.new(
77
+ :common => ResponseCommon,
78
+ :topic_fetch_responses => [TopicFetchResponse])
79
+
80
+ # Offset Request
81
+ PartitionOffsetRequest = ProtocolStruct.new(:partition => :int32,
82
+ :time => :int64,
83
+ :max_number_of_offsets => :int32)
84
+ TopicOffsetRequest = ProtocolStruct.new(
85
+ :topic => :string,
86
+ :partition_offset_requests => [PartitionOffsetRequest])
87
+ OffsetRequest = ProtocolStruct.new(:common => RequestCommon,
88
+ :replica_id => :int32,
89
+ :topic_offset_requests => [TopicOffsetRequest])
90
+
91
+ # Offset Response
92
+ Offset = ProtocolStruct.new(:offset => :int64)
93
+ PartitionOffset = ProtocolStruct.new(:partition => :int32,
94
+ :error => :int16,
95
+ :offsets => [Offset])
96
+ TopicOffsetResponse = ProtocolStruct.new(:topic => :string,
97
+ :partition_offsets => [PartitionOffset])
98
+ OffsetResponse = ProtocolStruct.new(
99
+ :common => ResponseCommon,
100
+ :topic_offset_responses => [TopicOffsetResponse])
101
+
102
+ # Metadata Request
103
+ MetadataRequest = ProtocolStruct.new( :common => RequestCommon,
104
+ :topic_names => [:string])
105
+
106
+ # Metadata Response
107
+ Broker = ProtocolStruct.new(:id => :int32,
108
+ :host => :string,
109
+ :port => :int32)
110
+ PartitionMetadata = ProtocolStruct.new(:error => :int16,
111
+ :id => :int32,
112
+ :leader => :int32,
113
+ :replicas => [:int32],
114
+ :isr => [:int32])
115
+ TopicMetadataStruct = ProtocolStruct.new(:error => :int16,
116
+ :name => :string,
117
+ :partitions => [PartitionMetadata])
118
+ MetadataResponse = ProtocolStruct.new(:common => ResponseCommon,
119
+ :brokers => [Broker],
120
+ :topics => [TopicMetadata])
121
+ end
122
+ end
@@ -0,0 +1,256 @@
1
+ module Poseidon
2
+ module Protocol
3
+ class ProtocolStruct < Struct
4
+ class EncodingError < StandardError;end
5
+ class DecodingError < StandardError;end
6
+
7
+ def self.new(hash)
8
+ klass = super(*hash.keys)
9
+ klass.type_map = hash
10
+ klass
11
+ end
12
+
13
+ def self.type_map=(type_map)
14
+ @type_map = type_map
15
+ end
16
+
17
+ def self.type_map
18
+ @type_map
19
+ end
20
+
21
+ def self.prepend_size
22
+ @prepend_size = true
23
+ self
24
+ end
25
+
26
+ def self.prepend_crc32
27
+ @prepend_crc32 = true
28
+ self
29
+ end
30
+
31
+ def self.truncatable
32
+ @truncatable = true
33
+ self
34
+ end
35
+
36
+ def self.prepend_size?
37
+ @prepend_size
38
+ end
39
+
40
+ def self.prepend_crc32?
41
+ @prepend_crc32
42
+ end
43
+
44
+ def self.truncatable?
45
+ @truncatable
46
+ end
47
+
48
+ def self.size_bound_array(member)
49
+ @size_bound_members ||= []
50
+ @size_bound_members << member
51
+ self
52
+ end
53
+
54
+ def self.size_bound_array?(member)
55
+ @size_bound_members ||= []
56
+ @size_bound_members.include?(member)
57
+ end
58
+
59
+ # Recursively find all objects with errors
60
+ def objects_with_errors
61
+ children = []
62
+ each_pair do |member, value|
63
+ case value
64
+ when Array
65
+ value.each do |v|
66
+ if v.respond_to?(:objects_with_errors)
67
+ children << v
68
+ end
69
+ end
70
+ else
71
+ if value.respond_to?(:objects_with_errors)
72
+ children << value
73
+ end
74
+ end
75
+ end
76
+
77
+ children_with_errors = children.map(&:objects_with_errors).flatten
78
+ if members.include?(:error) && self[:error] != Errors::NO_ERROR_CODE
79
+ children_with_errors + [self]
80
+ else
81
+ children_with_errors
82
+ end
83
+ end
84
+
85
+ def raise_error
86
+ raise error_class if error_class
87
+ end
88
+
89
+ def error_class
90
+ Errors::ERROR_CODES[self[:error]]
91
+ end
92
+
93
+ def raise_error_if_one_exists
94
+ objects_with_errors.each do |object|
95
+ object.raise_error
96
+ end
97
+ end
98
+
99
+ def write(buffer)
100
+ maybe_prepend_size(buffer) do
101
+ maybe_prepend_crc32(buffer) do
102
+ each_pair do |member, value|
103
+ begin
104
+ write_member(buffer, member, value)
105
+ rescue
106
+ raise EncodingError, "Error writting #{member} in #{self.class} (#{$!.class}: #{$!.message})"
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def maybe_prepend_size(buffer)
114
+ if self.class.prepend_size?
115
+ buffer.prepend_size do
116
+ yield
117
+ end
118
+ else
119
+ yield
120
+ end
121
+ end
122
+
123
+ def maybe_prepend_crc32(buffer)
124
+ if self.class.prepend_crc32?
125
+ buffer.prepend_crc32 do
126
+ yield
127
+ end
128
+ else
129
+ yield
130
+ end
131
+ end
132
+
133
+ def write_member(buffer, member, value)
134
+ case type = type_map[member]
135
+ when Array
136
+ buffer.int32(value.size) unless self.class.size_bound_array?(member)
137
+ value.each { |v| write_type(buffer, type.first, v) }
138
+ else
139
+ write_type(buffer, type, value)
140
+ end
141
+ end
142
+
143
+ def write_type(buffer, type, value)
144
+ case type
145
+ when Symbol
146
+ buffer.send(type, value)
147
+ else
148
+ value.write(buffer)
149
+ end
150
+ end
151
+
152
+ # Populate struct from buffer based on members and their type definition.
153
+ def self.read(buffer)
154
+ s = new
155
+ s.read(buffer)
156
+ s
157
+ end
158
+
159
+ def read(buffer)
160
+ if self.class.prepend_size?
161
+ if !have_header?(buffer)
162
+ @truncated = true
163
+ return
164
+ end
165
+
166
+ @size = buffer.int32
167
+
168
+ if self.class.prepend_crc32?
169
+ @crc32 = buffer.int32
170
+ @computed_crc32 = [Zlib::crc32(buffer.peek(@size-4))].pack("l>").unpack("l>").first
171
+ if @crc32 != @computed_crc32
172
+ @checksum_failed = true
173
+ end
174
+ expected_bytes_remaining = @size - 4
175
+ else
176
+ expected_bytes_remaining = @size
177
+ end
178
+
179
+ if self.class.truncatable? && expected_bytes_remaining > buffer.bytes_remaining
180
+ @truncated = true
181
+ return
182
+ end
183
+ end
184
+
185
+ members.each do |member|
186
+ begin
187
+ self[member] = read_member(buffer, member)
188
+ rescue DecodingError
189
+ # Just reraise instead of producing a crazy nested exception
190
+ raise
191
+ rescue
192
+ raise DecodingError, "Error while reading #{member} in #{self.class} (#{$!.class}: #{$!.message}))"
193
+ end
194
+ end
195
+ end
196
+
197
+ def have_header?(buffer)
198
+ if self.class.truncatable?
199
+ if self.class.prepend_crc32?
200
+ header_bytes = 8
201
+ else
202
+ header_bytes = 4
203
+ end
204
+
205
+ return buffer.bytes_remaining >= header_bytes
206
+ else
207
+ return true
208
+ end
209
+ end
210
+
211
+ def read_member(buffer, member)
212
+ case type = type_map[member]
213
+ when Array
214
+ if self.class.size_bound_array?(member)
215
+ if @size
216
+ array_buffer = ResponseBuffer.new(buffer.read(@size))
217
+ else
218
+ array_buffer = buffer
219
+ end
220
+
221
+ array = []
222
+ while !array_buffer.eof? && (v = read_type(array_buffer, type.first))
223
+ array << v
224
+ end
225
+ array
226
+ else
227
+ buffer.int32.times.map { read_type(buffer, type.first) }
228
+ end
229
+ else
230
+ read_type(buffer, type)
231
+ end
232
+ end
233
+
234
+ def read_type(buffer, type)
235
+ case type
236
+ when Symbol
237
+ buffer.send(type)
238
+ else
239
+ type.read(buffer)
240
+ end
241
+ end
242
+
243
+ def type_map
244
+ self.class.type_map
245
+ end
246
+
247
+ def checksum_failed?
248
+ @checksum_failed
249
+ end
250
+
251
+ def truncated?
252
+ @truncated
253
+ end
254
+ end
255
+ end
256
+ end