krakow 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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