ably 0.8.8 → 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -2
  3. data/LICENSE +2 -2
  4. data/README.md +81 -20
  5. data/SPEC.md +235 -178
  6. data/lib/ably/auth.rb +1 -1
  7. data/lib/ably/exceptions.rb +10 -1
  8. data/lib/ably/models/cipher_params.rb +114 -0
  9. data/lib/ably/models/connection_details.rb +8 -6
  10. data/lib/ably/models/error_info.rb +3 -3
  11. data/lib/ably/models/idiomatic_ruby_wrapper.rb +27 -20
  12. data/lib/ably/models/message.rb +15 -15
  13. data/lib/ably/models/message_encoders/cipher.rb +8 -7
  14. data/lib/ably/models/presence_message.rb +17 -17
  15. data/lib/ably/models/protocol_message.rb +26 -19
  16. data/lib/ably/models/stats.rb +15 -15
  17. data/lib/ably/models/token_details.rb +14 -12
  18. data/lib/ably/models/token_request.rb +16 -14
  19. data/lib/ably/modules/async_wrapper.rb +1 -1
  20. data/lib/ably/modules/encodeable.rb +10 -10
  21. data/lib/ably/modules/model_common.rb +13 -5
  22. data/lib/ably/realtime/channel.rb +1 -2
  23. data/lib/ably/realtime/presence.rb +29 -58
  24. data/lib/ably/realtime/presence/members_map.rb +2 -2
  25. data/lib/ably/rest/channel.rb +1 -2
  26. data/lib/ably/rest/middleware/exceptions.rb +14 -4
  27. data/lib/ably/rest/presence.rb +3 -1
  28. data/lib/ably/util/crypto.rb +50 -40
  29. data/lib/ably/version.rb +1 -1
  30. data/spec/acceptance/realtime/message_spec.rb +20 -20
  31. data/spec/acceptance/realtime/presence_history_spec.rb +7 -7
  32. data/spec/acceptance/realtime/presence_spec.rb +65 -77
  33. data/spec/acceptance/rest/auth_spec.rb +8 -8
  34. data/spec/acceptance/rest/base_spec.rb +4 -4
  35. data/spec/acceptance/rest/channel_spec.rb +1 -1
  36. data/spec/acceptance/rest/client_spec.rb +1 -1
  37. data/spec/acceptance/rest/encoders_spec.rb +4 -4
  38. data/spec/acceptance/rest/message_spec.rb +15 -15
  39. data/spec/acceptance/rest/presence_spec.rb +4 -4
  40. data/spec/shared/model_behaviour.rb +7 -7
  41. data/spec/unit/models/cipher_params_spec.rb +140 -0
  42. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +15 -8
  43. data/spec/unit/models/message_encoders/cipher_spec.rb +28 -22
  44. data/spec/unit/models/message_encoders/json_spec.rb +24 -0
  45. data/spec/unit/models/protocol_message_spec.rb +3 -3
  46. data/spec/unit/util/crypto_spec.rb +50 -17
  47. metadata +5 -2
@@ -2,7 +2,7 @@ require 'eventmachine'
2
2
 
3
3
  module Ably::Modules
4
4
  # Provides methods to convert synchronous operations into async operations through the use of
5
- # {EventMachine#defer http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine#defer-class_method}.
5
+ # {http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine#defer-class_method EventMachine#defer}.
6
6
  # The async_wrap method can only be called from within an EventMachine reactor, and must be thread safe.
7
7
  #
8
8
  # @note using this AsyncWrapper should only be used for methods that are used less frequently and typically
@@ -5,9 +5,9 @@ module Ably::Modules
5
5
  # Provides methods to allow this model's `data` property to be encoded and decoded based on the `encoding` property.
6
6
  #
7
7
  # This module expects the following:
8
- # - A #hash method that returns the underlying hash object
9
- # - A #set_hash_object(hash) method that updates the underlying hash object
10
- # - A #raw_hash_object attribute that returns the original hash used to create this object
8
+ # - A #attributes method that returns the underlying hash object
9
+ # - A #set_attributes_object(attributes) method that updates the underlying hash object
10
+ # - A #raw_hash_object attribute that returns the original hash object used to create this object
11
11
  #
12
12
  module Encodeable
13
13
  # Encode a message using the channel options and register encoders for the client
@@ -46,20 +46,20 @@ module Ably::Modules
46
46
 
47
47
  def apply_encoders(method, channel)
48
48
  max_encoding_length = 512
49
- message_hash = hash.dup
49
+ message_attributes = attributes.dup
50
50
 
51
51
  begin
52
- if message_hash[:encoding].to_s.length > max_encoding_length
53
- raise Ably::Exceptions::EncoderError("Encoding error, encoding value is too long: '#{message_hash[:encoding]}'", nil, 92100)
52
+ if message_attributes[:encoding].to_s.length > max_encoding_length
53
+ raise Ably::Exceptions::EncoderError("Encoding error, encoding value is too long: '#{message_attributes[:encoding]}'", nil, 92100)
54
54
  end
55
55
 
56
- previous_encoding = message_hash[:encoding]
56
+ previous_encoding = message_attributes[:encoding]
57
57
  channel.client.encoders.each do |encoder|
58
- encoder.send method, message_hash, channel.options
58
+ encoder.send method, message_attributes, channel.options
59
59
  end
60
- end until previous_encoding == message_hash[:encoding]
60
+ end until previous_encoding == message_attributes[:encoding]
61
61
 
62
- set_hash_object message_hash
62
+ set_attributes_object message_attributes
63
63
  rescue Ably::Exceptions::CipherError => cipher_error
64
64
  if channel.respond_to?(:emit)
65
65
  channel.client.logger.error "Encoder error #{cipher_error.code} trying to #{method} message: #{cipher_error.message}"
@@ -12,24 +12,32 @@ module Ably::Modules
12
12
  #
13
13
  # @return [Object]
14
14
  def [](key)
15
- hash[key]
15
+ attributes[key]
16
16
  end
17
17
 
18
18
  def ==(other)
19
19
  other.kind_of?(self.class) &&
20
- hash == other.hash
20
+ attributes == other.attributes
21
21
  end
22
22
 
23
- # Return a JSON ready object from the underlying #hash using Ably naming conventions for keys
23
+ # Return a JSON ready object from the underlying #attributes using Ably naming conventions for keys
24
+ # @return [Hash]
24
25
  def as_json
25
- hash.as_json.reject { |key, val| val.nil? }
26
+ attributes.as_json.reject { |key, val| val.nil? }
26
27
  end
27
28
 
28
- # Stringify the JSON representation of this object from the underlying #hash
29
+ # Stringify the JSON representation of this object from the underlying #attributes
30
+ # @return [String]
29
31
  def to_json(*args)
30
32
  as_json.to_json(*args)
31
33
  end
32
34
 
35
+ # @!attribute [r] hash
36
+ # @return [Integer] Compute a hash-code for this hash. Two hashes with the same content will have the same hash code
37
+ def hash
38
+ attributes.hash
39
+ end
40
+
33
41
  private
34
42
  def ensure_utf8_string_for(attribute, value)
35
43
  if value
@@ -82,8 +82,7 @@ module Ably
82
82
  # @param client [Ably::Rest::Client]
83
83
  # @param name [String] The name of the channel
84
84
  # @param channel_options [Hash] Channel options, currently reserved for Encryption options
85
- # @option channel_options [Boolean] :encrypted setting this to true for this channel will encrypt & decrypt all messages automatically
86
- # @option channel_options [Hash] :cipher_params A hash of options to configure the encryption. *:key* is required, all other options are optional. See {Ably::Util::Crypto#initialize} for a list of +cipher_params+ options
85
+ # @option channel_options [Hash,Ably::Models::CipherParams] :cipher A hash of options or a {Ably::Models::CipherParams} to configure the encryption. *:key* is required, all other options are optional. See {Ably::Util::Crypto#initialize} for a list of +:cipher+ options
87
86
  #
88
87
  def initialize(client, name, channel_options = {})
89
88
  ensure_utf_8 :name, name
@@ -59,26 +59,16 @@ module Ably::Realtime
59
59
  # Enter this client into this channel. This client will be added to the presence set
60
60
  # and presence subscribers will see an enter message for this client.
61
61
  #
62
- # @param [Hash] options an options Hash to specify client data and/or client ID
63
- # @option options [String] :data optional data (eg a status message) for this member
64
- # @option options [String] :client_id the optional id of the client.
65
- # This option is provided to support connections from server instances that act on behalf of
66
- # multiple client_ids. In order to be able to enter the channel with this method, the client
67
- # library must have been instanced either with a key, or with a token bound to the wildcard clientId.
62
+ # @param [String,Hash,nil] data optional data (eg a status message) for this member
68
63
  #
69
64
  # @yield [Ably::Realtime::Presence] On success, will call the block with this {Ably::Realtime::Presence} object
70
65
  # @return [Ably::Util::SafeDeferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
71
66
  #
72
- def enter(options = {}, &success_block)
73
- client_id = options.fetch(:client_id, self.client_id)
74
- data = options.fetch(:data, nil)
67
+ def enter(data = nil, &success_block)
75
68
  deferrable = create_deferrable
76
69
 
77
- ensure_supported_client_id client_id
78
- ensure_supported_payload data unless data.nil?
79
-
70
+ ensure_supported_payload data
80
71
  @data = data
81
- @client_id = client_id
82
72
 
83
73
  return deferrable_succeed(deferrable, &success_block) if state == STATE.Entered
84
74
 
@@ -94,8 +84,8 @@ module Ably::Realtime
94
84
  Ably::Models::PresenceMessage::ACTION.Enter,
95
85
  deferrable: deferrable,
96
86
  target_state: STATE.Entered,
97
- client_id: client_id,
98
87
  data: data,
88
+ client_id: client_id,
99
89
  failed_state: STATE.Failed,
100
90
  &success_block
101
91
  )
@@ -111,36 +101,30 @@ module Ably::Realtime
111
101
  # either with a key, or with a token bound to the wildcard client_id
112
102
  #
113
103
  # @param [String] client_id id of the client
114
- #
115
- # @param [Hash] options an options Hash for this client event
116
- # @option options [String] :data optional data (eg a status message) for this member
104
+ # @param [String,Hash,nil] data optional data (eg a status message) for this member
117
105
  #
118
106
  # @yield [Ably::Realtime::Presence] On success, will call the block with this {Ably::Realtime::Presence} object
119
107
  # @return [Ably::Util::SafeDeferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
120
108
  #
121
- def enter_client(client_id, options = {}, &success_block)
122
- raise ArgumentError, 'options must be a Hash' unless options.kind_of?(Hash)
109
+ def enter_client(client_id, data = nil, &success_block)
123
110
  ensure_supported_client_id client_id
124
- ensure_supported_payload options[:data] if options.has_key?(:data)
111
+ ensure_supported_payload data
125
112
 
126
- send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Enter, client_id, options, &success_block)
113
+ send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Enter, client_id, data, &success_block)
127
114
  end
128
115
 
129
116
  # Leave this client from this channel. This client will be removed from the presence
130
117
  # set and presence subscribers will see a leave message for this client.
131
118
  #
132
- # @param [Hash,String] options an options Hash to specify client data and/or client ID
133
- # @option options [String] :data optional data (eg a status message) for this member
119
+ # @param (see Presence#enter)
134
120
  #
135
121
  # @yield (see Presence#enter)
136
122
  # @return (see Presence#enter)
137
123
  #
138
- def leave(options = {}, &success_block)
139
- data = options.fetch(:data, self.data) # nil value defaults leave data to existing value
124
+ def leave(data = nil, &success_block)
140
125
  deferrable = create_deferrable
141
126
 
142
- ensure_supported_client_id client_id
143
- ensure_supported_payload data unless data.nil?
127
+ ensure_supported_payload data
144
128
  raise Ably::Exceptions::Standard.new('Unable to leave presence channel that is not entered', 400, 91002) unless able_to_leave?
145
129
 
146
130
  @data = data
@@ -159,8 +143,8 @@ module Ably::Realtime
159
143
  Ably::Models::PresenceMessage::ACTION.Leave,
160
144
  deferrable: deferrable,
161
145
  target_state: STATE.Left,
162
- client_id: client_id,
163
146
  data: data,
147
+ client_id: client_id,
164
148
  failed_state: STATE.Failed,
165
149
  &success_block
166
150
  )
@@ -172,35 +156,30 @@ module Ably::Realtime
172
156
  # presence set and presence subscribers will see a leave message for this client.
173
157
  #
174
158
  # @param (see Presence#enter_client)
175
- # @option options (see Presence#enter_client)
176
159
  #
177
160
  # @yield (see Presence#enter_client)
178
161
  # @return (see Presence#enter_client)
179
162
  #
180
- def leave_client(client_id, options = {}, &success_block)
181
- raise ArgumentError, 'options must be a Hash' unless options.kind_of?(Hash)
163
+ def leave_client(client_id, data = nil, &success_block)
182
164
  ensure_supported_client_id client_id
183
- ensure_supported_payload options[:data] if options.has_key?(:data)
165
+ ensure_supported_payload data
184
166
 
185
- send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Leave, client_id, options, &success_block)
167
+ send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Leave, client_id, data, &success_block)
186
168
  end
187
169
 
188
170
  # Update the presence data for this client. If the client is not already a member of
189
171
  # the presence set it will be added, and presence subscribers will see an enter or
190
172
  # update message for this client.
191
173
  #
192
- # @param [Hash,String] options an options Hash to specify client data
193
- # @option options [String] :data optional data (eg a status message) for this member
174
+ # @param (see Presence#enter)
194
175
  #
195
176
  # @yield (see Presence#enter)
196
177
  # @return (see Presence#enter)
197
178
  #
198
- def update(options = {}, &success_block)
199
- data = options.fetch(:data, nil)
179
+ def update(data = nil, &success_block)
200
180
  deferrable = create_deferrable
201
181
 
202
- ensure_supported_client_id client_id
203
- ensure_supported_payload data unless data.nil?
182
+ ensure_supported_payload data
204
183
 
205
184
  @data = data
206
185
 
@@ -224,17 +203,15 @@ module Ably::Realtime
224
203
  # enables it to represent an arbitrary clientId.
225
204
  #
226
205
  # @param (see Presence#enter_client)
227
- # @option options (see Presence#enter_client)
228
206
  #
229
207
  # @yield (see Presence#enter_client)
230
208
  # @return (see Presence#enter_client)
231
209
  #
232
- def update_client(client_id, options = {}, &success_block)
233
- raise ArgumentError, 'options must be a Hash' unless options.kind_of?(Hash)
210
+ def update_client(client_id, data = nil, &success_block)
234
211
  ensure_supported_client_id client_id
235
- ensure_supported_payload options[:data] if options.has_key?(:data)
212
+ ensure_supported_payload data
236
213
 
237
- send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Update, client_id, options, &success_block)
214
+ send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Update, client_id, data, &success_block)
238
215
  end
239
216
 
240
217
  # Get the presence state for this Channel.
@@ -344,8 +321,8 @@ module Ably::Realtime
344
321
  end
345
322
 
346
323
  # @return [Ably::Models::PresenceMessage] presence message is returned allowing callbacks to be added
347
- def send_presence_protocol_message(presence_action, client_id, options = {})
348
- presence_message = create_presence_message(presence_action, client_id, options)
324
+ def send_presence_protocol_message(presence_action, client_id, data)
325
+ presence_message = create_presence_message(presence_action, client_id, data)
349
326
  unless presence_message.client_id
350
327
  raise Ably::Exceptions::Standard.new('Unable to enter create presence message without a client_id', 400, 91000)
351
328
  end
@@ -361,12 +338,12 @@ module Ably::Realtime
361
338
  presence_message
362
339
  end
363
340
 
364
- def create_presence_message(action, client_id, options = {})
341
+ def create_presence_message(action, client_id, data)
365
342
  model = {
366
343
  action: Ably::Models::PresenceMessage.ACTION(action).to_i,
367
- clientId: client_id
344
+ clientId: client_id,
345
+ data: data
368
346
  }
369
- model.merge!(data: options.fetch(:data)) if options.has_key?(:data)
370
347
 
371
348
  Ably::Models::PresenceMessage.new(model, logger: logger).tap do |presence_message|
372
349
  presence_message.encode self.channel
@@ -406,13 +383,7 @@ module Ably::Realtime
406
383
  target_state = options.fetch(:target_state, nil)
407
384
  failed_state = options.fetch(:failed_state, nil)
408
385
 
409
- protocol_message_options = if options.has_key?(:data)
410
- { data: options.fetch(:data) }
411
- else
412
- { }
413
- end
414
-
415
- send_presence_protocol_message(action, client_id, protocol_message_options).tap do |protocol_message|
386
+ send_presence_protocol_message(action, client_id, options[:data]).tap do |protocol_message|
416
387
  protocol_message.callback do |message|
417
388
  change_state target_state, message if target_state
418
389
  deferrable_succeed deferrable, &success_block
@@ -437,12 +408,12 @@ module Ably::Realtime
437
408
  deferrable
438
409
  end
439
410
 
440
- def send_presence_action_for_client(action, client_id, options = {}, &success_block)
411
+ def send_presence_action_for_client(action, client_id, data, &success_block)
441
412
  ensure_presence_publishable_on_connection
442
413
 
443
414
  deferrable = create_deferrable
444
415
  ensure_channel_attached(deferrable) do
445
- send_presence_protocol_message(action, client_id, options).tap do |protocol_message|
416
+ send_presence_protocol_message(action, client_id, data).tap do |protocol_message|
446
417
  protocol_message.callback { |message| deferrable_succeed deferrable, &success_block }
447
418
  protocol_message.errback { |error| deferrable_fail deferrable, error }
448
419
  end
@@ -60,8 +60,8 @@ module Ably::Realtime
60
60
  # Get the list of presence members
61
61
  #
62
62
  # @param [Hash,String] options an options Hash to filter members
63
- # @option options [String] :client_id optional client_id for the member
64
- # @option options [String] :connection_id optional connection_id for the member
63
+ # @option options [String] :client_id optional client_id filter for the member
64
+ # @option options [String] :connection_id optional connection_id filter for the member
65
65
  # @option options [String] :wait_for_sync defaults to false, if true the get method waits for the initial presence sync following channel attachment to complete before returning the members present
66
66
  #
67
67
  # @yield [Array<Ably::Models::PresenceMessage>] array of present members
@@ -19,8 +19,7 @@ module Ably
19
19
  # @param client [Ably::Rest::Client]
20
20
  # @param name [String] The name of the channel
21
21
  # @param channel_options [Hash] Channel options, currently reserved for Encryption options
22
- # @option channel_options [Boolean] :encrypted setting this to true for this channel will encrypt & decrypt all messages automatically
23
- # @option channel_options [Hash] :cipher_params A hash of options to configure the encryption. *:key* is required, all other options are optional. See {Ably::Util::Crypto#initialize} for a list of +cipher_params+ options
22
+ # @option channel_options [Hash,Ably::Models::CipherParams] :cipher A hash of options or a {Ably::Models::CipherParams} to configure the encryption. *:key* is required, all other options are optional. See {Ably::Util::Crypto#initialize} for a list of +:cipher+ options
24
23
  #
25
24
  def initialize(client, name, channel_options = {})
26
25
  ensure_utf_8 :name, name
@@ -30,12 +30,22 @@ module Ably
30
30
 
31
31
  message = 'Unknown server error' if message.to_s.strip == ''
32
32
 
33
+ exception_args = [message, error_status_code, error_code]
34
+
33
35
  if env.status >= 500
34
- raise Ably::Exceptions::ServerError.new(message, error_status_code, error_code)
35
- elsif env.status == 401 && TOKEN_EXPIRED_CODE.include?(error_code)
36
- raise Ably::Exceptions::TokenExpired.new(message, error_status_code, error_code)
36
+ raise Ably::Exceptions::ServerError.new(*exception_args)
37
+ elsif env.status == 401
38
+ if TOKEN_EXPIRED_CODE.include?(error_code)
39
+ raise Ably::Exceptions::TokenExpired.new(*exception_args)
40
+ else
41
+ raise Ably::Exceptions::UnauthorizedRequest.new(*exception_args)
42
+ end
43
+ elsif env.status == 403
44
+ raise Ably::Exceptions::ForbiddenRequest.new(*exception_args)
45
+ elsif env.status == 404
46
+ raise Ably::Exceptions::ResourceMissing.new(*exception_args)
37
47
  else
38
- raise Ably::Exceptions::InvalidRequest.new(message, error_status_code, error_code)
48
+ raise Ably::Exceptions::InvalidRequest.new(*exception_args)
39
49
  end
40
50
  end
41
51
  end
@@ -23,7 +23,9 @@ module Ably
23
23
  # Obtain the set of members currently present for a channel
24
24
  #
25
25
  # @param [Hash] options the options for the set of members present
26
- # @option options [Integer] :limit Maximum number of members to retrieve up to 1,000, defaults to 100
26
+ # @option options [Integer] :limit Maximum number of members to retrieve up to 1,000, defaults to 100
27
+ # @option options [String] :client_id optional client_id filter for the member
28
+ # @option options [String] :connection_id optional connection_id filter for the member
27
29
  #
28
30
  # @return [Ably::Models::PaginatedResult<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResult page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResult#items #items}.
29
31
  #
@@ -4,52 +4,65 @@ require 'openssl'
4
4
  module Ably::Util
5
5
  class Crypto
6
6
  DEFAULTS = {
7
- algorithm: 'AES',
8
- mode: 'CBC',
9
- key_length: 128,
7
+ algorithm: 'aes',
8
+ mode: 'cbc',
9
+ key_length: 256,
10
10
  }
11
11
 
12
12
  BLOCK_LENGTH = 16
13
13
 
14
- # Configured options for this Crypto object, see {#initialize} for a list of configured options
14
+ # Configured {Ably::Models::CipherParams} for this Crypto object, see {#initialize} for a list of configureable options
15
15
  #
16
- # @return [Hash]
17
- attr_reader :options
16
+ # @return [Ably::Models::CipherParams]
17
+ attr_reader :cipher_params
18
18
 
19
19
  # Creates a {Ably::Util::Crypto} object
20
20
  #
21
- # @param [Hash] options an options Hash used to configure the Crypto library
22
- # @option options [String] :key Required secret key used for encrypting and decrypting
23
- # @option options [String] :algorithm optional (default AES), specify the encryption algorithm supported by {http://ruby-doc.org/stdlib-2.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html OpenSSL::Cipher}
24
- # @option options [String] :mode optional (default CBC), specify the cipher mode supported by {http://ruby-doc.org/stdlib-2.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html OpenSSL::Cipher}
25
- # @option options [Integer] :key_length optional (default 128), specify the key length of the cipher supported by {http://ruby-doc.org/stdlib-2.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html OpenSSL::Cipher}
26
- # @option options [String] :combined optional (default AES-128-CBC), specify in one option the algorithm, key length and cipher of the cipher supported by {http://ruby-doc.org/stdlib-2.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html OpenSSL::Cipher}
21
+ # @param [Hash] params a Hash used to configure the Crypto library's {Ably::Models::CipherParams}
22
+ # @option params (see Ably::Models::CipherParams#initialize)
27
23
  #
28
24
  # @return [Ably::Util::Crypto]
29
25
  #
30
26
  # @example
31
- # crypto = Ably::Util::Crypto.new(key: 'mysecret')
27
+ # key = Ably::Util::Crypto.generate_random_key
28
+ # crypto = Ably::Util::Crypto.new(key: key)
32
29
  # encrypted = crypto.encrypt('secret text')
33
30
  # crypto.decrypt(decrypted) # => 'secret text'
34
31
  #
35
- def initialize(options)
36
- raise ArgumentError, ':key is required' unless options.has_key?(:key)
37
- @options = DEFAULTS.merge(options).freeze
32
+ def initialize(params)
33
+ @fixed_iv = params.delete(:fixed_iv) if params.kind_of?(Hash)
34
+ @cipher_params = Ably::Models::CipherParams(params)
38
35
  end
39
36
 
40
- # Obtain a default CipherParams. This uses default algorithm, mode and
41
- # padding and key length. A key and IV are generated using the default
42
- # system SecureRandom; the key may be obtained from the returned CipherParams
37
+ # Obtain a default {Ably::Models::CipherParams}. This uses default algorithm, mode and
38
+ # padding and key length. An IV is generated using the default
39
+ # system SecureRandom; the key may be obtained from the returned {Ably::Models::CipherParams}
43
40
  # for out-of-band distribution to other clients.
41
+
42
+ # @param [Hash] params a Hash used to configure the Crypto library's {Ably::Models::CipherParams}
43
+ # @option params (see Ably::Models::CipherParams#initialize)
44
+ #
45
+ # @return [Ably::Models::CipherParams] Configured cipher params with :key, :algorithm, :mode, :key_length attributes
46
+ #
47
+ def self.get_default_params(params = {})
48
+ Ably::Models::CipherParams(params)
49
+ end
50
+
51
+ # Generate a random encryption key from the supplied keylength (or the
52
+ # default key_length of 256 if none supplied)
44
53
  #
45
- # @return [Hash] CipherParam options Hash with attributes :key, :algorithn, :mode, :key_length
54
+ # @param [Integer] key_length Optional (default 256) key length for the generated random key. 128 and 256 bit key lengths are supported
55
+ # @return Binary String (byte array) with ASCII_8BIT encoding
46
56
  #
47
- def self.get_default_params(key = nil)
48
- params = DEFAULTS.merge(key: key)
49
- params[:key_length] = key.unpack('b*').first.length if params[:key]
50
- cipher_type = "#{params[:algorithm]}-#{params[:key_length]}-#{params[:mode]}"
51
- params[:key] = OpenSSL::Cipher.new(cipher_type.upcase).random_key unless params[:key]
52
- params
57
+ def self.generate_random_key(key_length = DEFAULTS.fetch(:key_length))
58
+ params = DEFAULTS.merge(key_length: key_length)
59
+ OpenSSL::Cipher.new(cipher_type(params)).random_key
60
+ end
61
+
62
+ # The Cipher algorithm string such as AES-128-CBC
63
+ # @api private
64
+ def self.cipher_type(options)
65
+ Ably::Models::CipherParams.cipher_type(options)
53
66
  end
54
67
 
55
68
  # Encrypt payload using configured Cipher
@@ -58,13 +71,13 @@ module Ably::Util
58
71
  # @param [Hash] encrypt_options an options Hash to configure the encrypt action
59
72
  # @option encrypt_options [String] :iv optionally use the provided Initialization Vector instead of a randomly generated IV
60
73
  #
61
- # @return [String] binary string with {Encoding::ASCII_8BIT} encoding
74
+ # @return [String] binary string with +Encoding::ASCII_8BIT+ encoding
62
75
  #
63
76
  def encrypt(payload, encrypt_options = {})
64
77
  cipher = openssl_cipher
65
78
  cipher.encrypt
66
79
  cipher.key = key
67
- iv = encrypt_options[:iv] || options[:iv] || cipher.random_iv
80
+ iv = encrypt_options[:iv] || fixed_iv || cipher.random_iv
68
81
  cipher.iv = iv
69
82
 
70
83
  iv << cipher.update(payload) << cipher.final
@@ -90,31 +103,28 @@ module Ably::Util
90
103
  decipher.update(encrypted_payload) << decipher.final
91
104
  end
92
105
 
93
- # Generate a random key
94
- # @return [String]
95
- def random_key
96
- openssl_cipher.random_key
97
- end
98
-
99
106
  # Generate a random IV
100
107
  # @return [String]
101
108
  def random_iv
102
109
  openssl_cipher.random_iv
103
110
  end
104
111
 
105
- # The Cipher algorithm string such as AES-128-CBC
112
+ private
113
+ # Used solely for tests to fix the IV instead of randomly generate one
114
+ attr_reader :fixed_iv
115
+
116
+ # Generate a random key
106
117
  # @return [String]
107
- def cipher_type
108
- (options[:combined] || "#{options[:algorithm]}-#{options[:key_length]}-#{options[:mode]}").to_s.upcase
118
+ def random_key
119
+ openssl_cipher.random_key
109
120
  end
110
121
 
111
- private
112
122
  def key
113
- options[:key]
123
+ cipher_params.key
114
124
  end
115
125
 
116
126
  def openssl_cipher
117
- OpenSSL::Cipher.new(cipher_type)
127
+ @openssl_cipher ||= OpenSSL::Cipher.new(cipher_params.cipher_type)
118
128
  end
119
129
  end
120
130
  end