krakow 0.2.2 → 0.3.0

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 (44) hide show
  1. data/CHANGELOG.md +16 -0
  2. data/CONTRIBUTING.md +25 -0
  3. data/LICENSE +13 -0
  4. data/README.md +62 -9
  5. data/krakow.gemspec +3 -1
  6. data/lib/krakow/command/cls.rb +3 -4
  7. data/lib/krakow/command/fin.rb +13 -4
  8. data/lib/krakow/command/identify.rb +22 -9
  9. data/lib/krakow/command/mpub.rb +14 -4
  10. data/lib/krakow/command/nop.rb +3 -4
  11. data/lib/krakow/command/pub.rb +15 -5
  12. data/lib/krakow/command/rdy.rb +13 -4
  13. data/lib/krakow/command/req.rb +14 -4
  14. data/lib/krakow/command/sub.rb +14 -4
  15. data/lib/krakow/command/touch.rb +13 -4
  16. data/lib/krakow/command.rb +25 -3
  17. data/lib/krakow/connection.rb +286 -60
  18. data/lib/krakow/connection_features/deflate.rb +26 -1
  19. data/lib/krakow/connection_features/snappy_frames.rb +34 -3
  20. data/lib/krakow/connection_features/ssl.rb +43 -1
  21. data/lib/krakow/connection_features.rb +1 -0
  22. data/lib/krakow/consumer.rb +162 -49
  23. data/lib/krakow/discovery.rb +17 -6
  24. data/lib/krakow/distribution/default.rb +61 -33
  25. data/lib/krakow/distribution.rb +107 -57
  26. data/lib/krakow/exceptions.rb +14 -0
  27. data/lib/krakow/frame_type/error.rb +13 -7
  28. data/lib/krakow/frame_type/message.rb +47 -4
  29. data/lib/krakow/frame_type/response.rb +14 -4
  30. data/lib/krakow/frame_type.rb +20 -8
  31. data/lib/krakow/producer/http.rb +95 -6
  32. data/lib/krakow/producer.rb +60 -17
  33. data/lib/krakow/utils/lazy.rb +99 -40
  34. data/lib/krakow/utils/logging.rb +11 -0
  35. data/lib/krakow/utils.rb +3 -0
  36. data/lib/krakow/version.rb +3 -1
  37. data/lib/krakow.rb +1 -0
  38. metadata +11 -11
  39. data/Gemfile +0 -5
  40. data/Gemfile.lock +0 -34
  41. data/test/spec.rb +0 -81
  42. data/test/specs/consumer.rb +0 -49
  43. data/test/specs/http_producer.rb +0 -123
  44. data/test/specs/producer.rb +0 -20
@@ -1,13 +1,29 @@
1
- require 'krakow/version'
1
+ require 'krakow'
2
2
  require 'celluloid/io'
3
- require 'celluloid/autostart'
4
3
 
5
4
  module Krakow
5
+
6
+ # Provides TCP connection to NSQD
6
7
  class Connection
7
8
 
9
+ # Generate identifier for connection
10
+ #
11
+ # @param host [String]
12
+ # @param port [String, Integer]
13
+ # @param topic [String]
14
+ # @param channel [String]
15
+ # @return [String]
16
+ def self.identifier(host, port, topic, channel)
17
+ [host, port, topic, channel].compact.join('__')
18
+ end
19
+
8
20
  include Utils::Lazy
21
+ # @!parse include Krakow::Utils::Lazy::InstanceMethods
22
+ # @!parse extend Krakow::Utils::Lazy::ClassMethods
23
+
9
24
  include Celluloid::IO
10
25
 
26
+ # Available connection features
11
27
  FEATURES = [
12
28
  :max_rdy_count,
13
29
  :max_msg_timeout,
@@ -19,63 +35,125 @@ module Krakow
19
35
  :snappy,
20
36
  :sample_rate
21
37
  ]
38
+
39
+ # List of features that may not be enabled together
22
40
  EXCLUSIVE_FEATURES = [[:snappy, :deflate]]
41
+
42
+ # List of features that may be enabled by the client
23
43
  ENABLEABLE_FEATURES = [:tls_v1, :snappy, :deflate]
24
44
 
25
45
  finalizer :goodbye_my_love!
26
46
 
27
- attr_reader :socket, :endpoint_settings
47
+ # @return [Hash] current configuration for endpoint
48
+ attr_reader :endpoint_settings
49
+ # @return [Socket-ish] underlying socket like instance
50
+ attr_reader :socket
51
+
52
+ attr_reader :connector, :reconnector, :reconnect_notifier, :responder, :running
53
+
54
+ # @!group Attributes
28
55
 
56
+ # @!macro [attach] attribute
57
+ # @!method $1
58
+ # @return [$2] the $1 $0
59
+ # @!method $1?
60
+ # @return [TrueClass, FalseClass] truthiness of the $1 $0
61
+ attribute :host, String, :required => true
62
+ attribute :port, [String,Integer], :required => true
63
+ attribute :topic, String
64
+ attribute :channel, String
65
+ attribute :version, String, :default => 'v2'
66
+ attribute :queue, Queue, :default => ->{ Queue.new }
67
+ attribute :callbacks, Hash, :default => ->{ Hash.new }
68
+ attribute :responses, Queue, :default => ->{ Queue.new }
69
+ attribute :notifier, Celluloid::Actor
70
+ attribute :features, Hash, :default => ->{ Hash.new }
71
+ attribute :response_wait, Numeric, :default => 1.0
72
+ attribute :response_interval, Numeric, :default => 0.03
73
+ attribute :error_wait, Numeric, :default => 0
74
+ attribute :enforce_features, [TrueClass,FalseClass], :default => true
75
+ attribute :features_args, Hash, :default => ->{ Hash.new }
76
+
77
+ # @!endgroup
78
+
79
+ # Create new instance
80
+ #
81
+ # @param args [Hash]
82
+ # @option args [String] :host (required) server host
83
+ # @option args [String, Numeric] :port (required) server port
84
+ # @option args [String] :version
85
+ # @option args [Queue] :queue received message queue
86
+ # @option args [Hash] :callbacks
87
+ # @option args [Queue] :responses received responses queue
88
+ # @option args [Celluloid::Actor] :notifier actor to notify on new message
89
+ # @option args [Hash] :features features to enable
90
+ # @option args [Numeric] :response_wait time to wait for response
91
+ # @option args [Numeric] :response_interval sleep interval for wait loop
92
+ # @option args [Numeric] :error_wait time to wait for error response
93
+ # @option args [TrueClass, FalseClass] :enforce_features fail if features are unavailable
94
+ # @option args [Hash] :feature_args options for connection features
29
95
  def initialize(args={})
30
96
  super
31
- required! :host, :port
32
- optional(
33
- :version, :queue, :callback, :responses, :notifier,
34
- :features, :response_wait, :error_wait, :enforce_features
35
- )
36
- arguments[:queue] ||= Queue.new
37
- arguments[:responses] ||= Queue.new
38
- arguments[:version] ||= 'v2'
39
- arguments[:features] ||= {}
40
- arguments[:response_wait] ||= 1
41
- arguments[:error_wait] ||= 0.4
42
- if(arguments[:enforce_features].nil?)
43
- arguments[:enforce_features] = true
44
- end
45
- @socket = TCPSocket.new(host, port)
97
+ @connector = Mutex.new
98
+ @reconnector = Mutex.new
99
+ @responder = Mutex.new
100
+ @reconnect_notifier = Celluloid::Signals.new
101
+ @socket_retries = 0
102
+ @socket_max_retries = 10
103
+ @reconnect_pause = 0.5
46
104
  @endpoint_settings = {}
105
+ @running = false
47
106
  end
48
107
 
108
+ # @return [String] identifier for this connection
109
+ def identifier
110
+ self.class.identifier(host, port, topic, channel)
111
+ end
112
+
113
+ # @return [String] stringify object
49
114
  def to_s
50
115
  "<#{self.class.name}:#{object_id} {#{host}:#{port}}>"
51
116
  end
52
117
 
53
118
  # Initialize the connection
119
+ #
120
+ # @return [nil]
54
121
  def init!
55
- debug 'Initializing connection'
56
- socket.write version.rjust(4).upcase
57
- identify_and_negotiate
58
- async.process_to_queue!
59
- info 'Connection initialized'
122
+ connector.synchronize do
123
+ connect!
124
+ end
125
+ nil
60
126
  end
61
127
 
62
- # message:: Command instance to send
63
- # Send the message
64
- # TODO: Do we want to validate Command instance and abort if
65
- # response is already set?
128
+ # Send message to remote server
129
+ #
130
+ # @param message [Krakow::Message] message to send
131
+ # @return [TrueClass, Krakow::FrameType] response if expected or true
66
132
  def transmit(message)
67
133
  output = message.to_line
68
- debug ">>> #{output}"
69
- socket.write output
70
134
  response_wait = wait_time_for(message)
71
- responses.clear if response_wait
72
- if(response_wait)
135
+ if(response_wait > 0)
136
+ transmit_with_response(message, response_wait)
137
+ else
138
+ debug ">>> #{output}"
139
+ safe_socket{|socket| socket.write output }
140
+ true
141
+ end
142
+ end
143
+
144
+ # Sends message and waits for response
145
+ #
146
+ # @param message [Krakow::Message] message to send
147
+ # @return [Krakow::FrameType] response
148
+ def transmit_with_response(message, wait_time)
149
+ responder.synchronize do
150
+ safe_socket{|socket| socket.write(message.to_line) }
151
+ responses.clear
73
152
  response = nil
74
- (response_wait / 0.1).to_i.times do |i|
153
+ (wait_time / response_interval).to_i.times do |i|
75
154
  response = responses.pop unless responses.empty?
76
155
  break if response
77
- debug "Response wait sleep for 0.1 seconds (#{i+1} time)"
78
- sleep(0.1)
156
+ sleep(response_interval)
79
157
  end
80
158
  if(response)
81
159
  message.response = response
@@ -90,23 +168,32 @@ module Krakow
90
168
  abort Error::BadResponse::NoResponse.new "No response provided for message #{message}"
91
169
  end
92
170
  end
93
- else
94
- true
95
171
  end
96
172
  end
97
173
 
98
- # Cleanup prior to destruction
174
+ # Destructor method for cleanup
175
+ #
176
+ # @return [nil]
99
177
  def goodbye_my_love!
100
178
  debug 'Tearing down connection'
101
179
  if(socket && !socket.closed?)
102
- socket.write Command::Cls.new.to_line
103
- socket.close
180
+ [lambda{ socket.write Command::Cls.new.to_line}, lambda{socket.close}].each do |action|
181
+ begin
182
+ action.call
183
+ rescue IOError, SystemCallError => e
184
+ warn "Socket error encountered during teardown: #{e.class}: #{e}"
185
+ end
186
+ end
104
187
  end
105
188
  @socket = nil
106
189
  info 'Connection torn down'
190
+ nil
107
191
  end
108
192
 
109
- # Receive message and return proper FrameType instance
193
+ # Receive from server
194
+ #
195
+ # @return [Krakow::FrameType, nil] message or nothing if read was empty
196
+ # @raise [Error::ConnectionUnavailable] socket is closed
110
197
  def receive
111
198
  debug 'Read wait for frame start'
112
199
  buf = socket.recv(8)
@@ -115,7 +202,7 @@ module Krakow
115
202
  debug "<<< #{buf.inspect}"
116
203
  struct = FrameType.decode(buf)
117
204
  debug "Decoded structure: #{struct.inspect}"
118
- struct[:data] = socket.recv(struct[:size])
205
+ struct[:data] = socket.read(struct[:size])
119
206
  debug "<<< #{struct[:data].inspect}"
120
207
  @receiving = false
121
208
  frame = FrameType.build(struct)
@@ -123,31 +210,43 @@ module Krakow
123
210
  frame
124
211
  else
125
212
  if(socket.closed?)
126
- raise Error.new("#{self} encountered closed socket!")
213
+ abort Error::ConnectionUnavailable.new("#{self} encountered closed socket!")
127
214
  end
128
215
  nil
129
216
  end
130
217
  end
131
218
 
132
- # Currently in the process of receiving a message
219
+ # @return [TrueClass, FalseClass] is connection currently receiving a message
133
220
  def receiving?
134
221
  !!@receiving
135
222
  end
136
223
 
137
- # Pull message and queue
224
+ # Receive messages and place into queue
225
+ #
226
+ # @return [nil]
138
227
  def process_to_queue!
139
- loop do
140
- message = handle(receive)
141
- if(message)
142
- debug "Adding message to queue #{message}"
143
- queue << message
144
- notifier.signal(message) if notifier
228
+ @running = true
229
+ while(@running)
230
+ begin
231
+ message = handle(receive)
232
+ if(message)
233
+ debug "Adding message to queue #{message}"
234
+ queue << message
235
+ notifier.signal(message) if notifier
236
+ end
237
+ rescue Error::ConnectionUnavailable => e
238
+ warn "Failed to receive message: #{e.class} - #{e}"
239
+ @running = false
240
+ async.reconnect!
145
241
  end
146
242
  end
243
+ nil
147
244
  end
148
245
 
149
- # message:: FrameType instance
150
- # Handle message if not an actual message
246
+ # Handle non-message type Krakow::FrameType
247
+ #
248
+ # @param message [Krakow::FrameType] received message
249
+ # @return [Krakow::FrameType, nil]
151
250
  def handle(message)
152
251
  # Grab heartbeats upfront
153
252
  if(message.is_a?(FrameType::Response) && message.response == '_heartbeat_')
@@ -155,10 +254,7 @@ module Krakow
155
254
  transmit Command::Nop.new
156
255
  nil
157
256
  else
158
- if(callback && callback[:actor] && callback[:method])
159
- debug "Sending #{message} to callback `#{callback[:actor]}##{callback[:method]}`"
160
- message = callback[:actor].send(callback[:method], message, current_actor)
161
- end
257
+ message = callback_for(:handle, message)
162
258
  if(!message.is_a?(FrameType::Message))
163
259
  debug "Captured non-message type response: #{message}"
164
260
  responses << message
@@ -169,6 +265,28 @@ module Krakow
169
265
  end
170
266
  end
171
267
 
268
+ # Execute callback for given type
269
+ #
270
+ # @overload callback_for(type, arg, connection)
271
+ # @param type [Symbol] type of callback
272
+ # @param arg [Object] argument for callback (can be multiple)
273
+ # @param connection [Krakow::Connection] current connection
274
+ # @return [Object] result of callback
275
+ def callback_for(type, *args)
276
+ callback = callbacks[type]
277
+ if(callback)
278
+ debug "Processing connection callback for #{type.inspect} (#{callback.inspect})"
279
+ callback[:actor].send(callback[:method], *(args + [current_actor]))
280
+ else
281
+ debug "No connection callback defined for #{type.inspect}"
282
+ args.size == 1 ? args.first : args
283
+ end
284
+ end
285
+
286
+ # Returns configured wait time for given message type
287
+ #
288
+ # @param message [Krakow::Command]
289
+ # @return [Numeric] seconds to wait
172
290
  def wait_time_for(message)
173
291
  case Command.response_for(message)
174
292
  when :required
@@ -178,6 +296,7 @@ module Krakow
178
296
  end
179
297
  end
180
298
 
299
+ # @return [Hash] default settings for IDENTIFY
181
300
  def identify_defaults
182
301
  unless(@identify_defaults)
183
302
  @identify_defaults = {
@@ -190,12 +309,15 @@ module Krakow
190
309
  @identify_defaults
191
310
  end
192
311
 
312
+ # IDENTIFY with server and negotiate features
313
+ #
314
+ # @return [TrueClass]
193
315
  def identify_and_negotiate
194
316
  expected_features = identify_defaults.merge(features)
195
317
  ident = Command::Identify.new(
196
318
  expected_features
197
319
  )
198
- socket.write(ident.to_line)
320
+ safe_socket{|socket| socket.write(ident.to_line) }
199
321
  response = receive
200
322
  if(expected_features[:feature_negotiation])
201
323
  begin
@@ -219,25 +341,129 @@ module Krakow
219
341
  true
220
342
  end
221
343
 
344
+ # Enable snappy feature on underlying socket
345
+ #
346
+ # @return [TrueClass]
222
347
  def snappy
223
348
  info 'Loading support for snappy compression and converting connection'
224
- @socket = ConnectionFeatures::SnappyFrames::Io.new(socket)
349
+ @socket = ConnectionFeatures::SnappyFrames::Io.new(socket, features_args)
225
350
  response = receive
226
351
  info "Snappy connection conversion complete. Response: #{response.inspect}"
352
+ true
227
353
  end
228
354
 
355
+ # Enable deflate feature on underlying socket
356
+ #
357
+ # @return [TrueClass]
229
358
  def deflate
230
359
  debug 'Loading support for deflate compression and converting connection'
231
- @socket = ConnectionFeatures::Deflate::Io.new(socket)
360
+ @socket = ConnectionFeatures::Deflate::Io.new(socket, features_args)
232
361
  response = receive
233
362
  info "Deflate connection conversion complete. Response: #{response.inspect}"
363
+ true
234
364
  end
235
365
 
366
+ # Enable TLS feature on underlying socket
367
+ #
368
+ # @return [TrueClass]
236
369
  def tls_v1
237
370
  info 'Enabling TLS for connection'
238
- @socket = ConnectionFeatures::Ssl::Io.new(socket)
371
+ @socket = ConnectionFeatures::Ssl::Io.new(socket, features_args)
239
372
  response = receive
240
373
  info "TLS enable complete. Response: #{response.inspect}"
374
+ true
375
+ end
376
+
377
+ # @return [TrueClass, FalseClass] underlying socket is connected
378
+ def connected?
379
+ socket && !socket.closed?
380
+ end
381
+
382
+ protected
383
+
384
+ # Destruct the underlying socket
385
+ #
386
+ # @return [nil]
387
+ def teardown_socket
388
+ if(socket && (socket.closed? || socket.eof?))
389
+ socket.close unless socket.closed?
390
+ @socket = nil
391
+ warn 'Existing socket instance has been destroyed from this connection'
392
+ end
393
+ nil
394
+ end
395
+
396
+ # Provides socket failure state handling around given block
397
+ #
398
+ # @yield [socket] execute within socket safety layer
399
+ # @yieldparam [socket] underlying socket
400
+ # @return [Object] result of executed block
401
+ def safe_socket(*args)
402
+ begin
403
+ if(socket.nil? || socket.closed?)
404
+ raise Error::ConnectionUnavailable.new 'Current connection is closed!'
405
+ end
406
+ result = yield socket if block_given?
407
+ result
408
+ rescue Error::ConnectionUnavailable, SystemCallError, IOError => e
409
+ warn "Safe socket encountered error (socket in failed state): #{e.class}: #{e}"
410
+ reconnect!
411
+ retry
412
+ rescue Celluloid::Error => e
413
+ warn "Internal error encountered. Allowing exception to bubble. #{e.class}: #{e}"
414
+ abort e
415
+ rescue Exception => e
416
+ warn "!!! Unexpected error encountered within safe socket: #{e.class}: #{e}"
417
+ raise
418
+ end
419
+ end
420
+
421
+ # Reconnect the underlying socket
422
+ #
423
+ # @return [nil]
424
+ def reconnect!
425
+ if(reconnector.try_lock)
426
+ begin
427
+ if(@socket_max_retries <= @socket_retries)
428
+ abort ConnectionFailure.new "Failed to re-establish connection after #{@socket_retries} tries."
429
+ end
430
+ pause_interval = @reconnect_pause * @socket_retries
431
+ @socket_retries += 1
432
+ warn "Pausing for #{pause_interval} seconds before reconnect"
433
+ sleep(pause_interval)
434
+ init!
435
+ @socket_retries = 0
436
+ rescue Celluloid::Error => e
437
+ warn "Internal error encountered. Allowing exception to bubble. #{e.class}: #{e}"
438
+ abort e
439
+ rescue SystemCallError, IOError => e
440
+ error "Reconnect error encountered: #{e.class} - #{e}"
441
+ retry
442
+ end
443
+ callback_for(:reconnect)
444
+ reconnect_notifier.broadcast(:connected)
445
+ reconnector.unlock
446
+ else
447
+ reconnect_notifier.wait(:connected)
448
+ end
449
+ nil
450
+ end
451
+
452
+ # Connect the underlying socket
453
+ #
454
+ # @return [nil]
455
+ def connect!
456
+ debug 'Initializing connection'
457
+ if(@socket)
458
+ @socket.close unless @socket.closed?
459
+ @socket = nil
460
+ end
461
+ @socket = Celluloid::IO::TCPSocket.new(host, port)
462
+ safe_socket{|socket| socket.write version.rjust(4).upcase}
463
+ identify_and_negotiate
464
+ async.process_to_queue!
465
+ info 'Connection initialized'
466
+ nil
241
467
  end
242
468
 
243
469
  end
@@ -1,13 +1,20 @@
1
1
  require 'zlib'
2
+ require 'krakow'
2
3
 
3
4
  module Krakow
4
5
  module ConnectionFeatures
6
+ # Deflate functionality
5
7
  module Deflate
8
+ # Deflatable IO
6
9
  class Io
7
10
 
8
11
  attr_reader :io, :buffer, :headers, :inflator, :deflator
9
12
 
10
- def initialize(io)
13
+ # Create new deflatable IO
14
+ #
15
+ # @param io [IO] IO to wrap
16
+ # @return [Io]
17
+ def initialize(io, args={})
11
18
  @io = io
12
19
  @buffer = ''
13
20
  @inflator = Zlib::Inflate.new(-Zlib::MAX_WBITS)
@@ -15,10 +22,17 @@ module Krakow
15
22
  end
16
23
 
17
24
  # Proxy to underlying socket
25
+ #
26
+ # @param args [Object]
27
+ # @return [Object]
18
28
  def method_missing(*args)
19
29
  io.__send__(*args)
20
30
  end
21
31
 
32
+ # Receive bytes from the IO
33
+ #
34
+ # @param n [Integer] nuber of bytes
35
+ # @return [String]
22
36
  def recv(n)
23
37
  until(buffer.length >= n)
24
38
  read_stream
@@ -28,6 +42,9 @@ module Krakow
28
42
  end
29
43
  alias_method :read, :recv
30
44
 
45
+ # Read contents from stream
46
+ #
47
+ # @return [String]
31
48
  def read_stream
32
49
  str = io.read
33
50
  unless(str.empty?)
@@ -35,6 +52,10 @@ module Krakow
35
52
  end
36
53
  end
37
54
 
55
+ # Write string to IO
56
+ #
57
+ # @param string [String]
58
+ # @return [Integer] number of bytes written
38
59
  def write(string)
39
60
  unless(string.empty?)
40
61
  output = deflator.deflate(string)
@@ -45,10 +66,14 @@ module Krakow
45
66
  end
46
67
  end
47
68
 
69
+ # Close the IO
70
+ #
71
+ # @return [TrueClass]
48
72
  def close(*args)
49
73
  super
50
74
  deflator.deflate(nil, Zlib::FINISH)
51
75
  deflator.close
76
+ true
52
77
  end
53
78
 
54
79
  end
@@ -1,18 +1,24 @@
1
1
  require 'snappy'
2
2
  require 'digest/crc'
3
+ require 'krakow'
3
4
 
4
- # TODO: Add support for max size + chunks
5
- # TODO: Include support for remaining types
6
5
  module Krakow
7
6
  module ConnectionFeatures
7
+ # Snappy functionality
8
+ # @todo Add support for max size + chunks
9
+ # @todo Include support for remaining types
8
10
  module SnappyFrames
11
+ # Snappy-able IO
9
12
  class Io
10
13
 
14
+ # Header identifier
11
15
  IDENTIFIER = "\x73\x4e\x61\x50\x70\x59".force_encoding('ASCII-8BIT')
12
16
  ident_size = [IDENTIFIER.size].pack('L<')
13
17
  ident_size.slice!(-1,1)
18
+ # Size of identifier
14
19
  IDENTIFIER_SIZE = ident_size
15
20
 
21
+ # Mapping of types
16
22
  CHUNK_TYPE = {
17
23
  "\xff".force_encoding('ASCII-8BIT') => :identifier,
18
24
  "\x00".force_encoding('ASCII-8BIT') => :compressed,
@@ -21,21 +27,36 @@ module Krakow
21
27
 
22
28
  attr_reader :io, :buffer
23
29
 
24
- def initialize(io)
30
+ # Create new snappy-able IO
31
+ #
32
+ # @param io [IO] IO to wrap
33
+ # @return [Io]
34
+ def initialize(io, args={})
25
35
  @io = io
26
36
  @snappy_write_ident = false
27
37
  @buffer = ''
28
38
  end
29
39
 
30
40
  # Proxy to underlying socket
41
+ #
42
+ # @param args [Object]
43
+ # @return [Object]
31
44
  def method_missing(*args)
32
45
  io.__send__(*args)
33
46
  end
34
47
 
48
+ # Mask the checksum
49
+ #
50
+ # @param checksum [String]
51
+ # @return [String]
35
52
  def checksum_mask(checksum)
36
53
  (((checksum >> 15) | (checksum << 17)) + 0xa282ead8) & 0xffffffff
37
54
  end
38
55
 
56
+ # Receive bytes from the IO
57
+ #
58
+ # @param n [Integer] nuber of bytes
59
+ # @return [String]
39
60
  def recv(n)
40
61
  read_stream unless buffer.size >= n
41
62
  result = buffer.slice!(0,n)
@@ -43,6 +64,9 @@ module Krakow
43
64
  end
44
65
  alias_method :read, :recv
45
66
 
67
+ # Read contents from stream
68
+ #
69
+ # @return [String]
46
70
  def read_stream
47
71
  header = io.recv(4)
48
72
  ident = CHUNK_TYPE[header.slice!(0)]
@@ -68,6 +92,10 @@ module Krakow
68
92
  end
69
93
  end
70
94
 
95
+ # Write string to IO
96
+ #
97
+ # @param string [String]
98
+ # @return [Integer] number of bytes written
71
99
  def write(string)
72
100
  unless(@snappy_writer_ident)
73
101
  send_snappy_identifier
@@ -83,6 +111,9 @@ module Krakow
83
111
  io.write output
84
112
  end
85
113
 
114
+ # Send the identifier for snappy content
115
+ #
116
+ # @return [Integer] bytes written
86
117
  def send_snappy_identifier
87
118
  io.write [CHUNK_TYPE.key(:identifier), IDENTIFIER_SIZE, IDENTIFIER].pack('a*a*a*')
88
119
  end