ably 0.8.8 → 0.8.9

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 (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