ably 0.6.2 → 0.7.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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.ruby-version.old +1 -0
- data/.travis.yml +0 -2
- data/Rakefile +22 -4
- data/SPEC.md +1676 -0
- data/ably.gemspec +1 -1
- data/lib/ably.rb +0 -8
- data/lib/ably/auth.rb +54 -46
- data/lib/ably/exceptions.rb +19 -5
- data/lib/ably/logger.rb +1 -1
- data/lib/ably/models/error_info.rb +1 -1
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +11 -9
- data/lib/ably/models/message.rb +15 -12
- data/lib/ably/models/message_encoders/base.rb +6 -5
- data/lib/ably/models/message_encoders/base64.rb +1 -0
- data/lib/ably/models/message_encoders/cipher.rb +6 -3
- data/lib/ably/models/message_encoders/json.rb +1 -0
- data/lib/ably/models/message_encoders/utf8.rb +2 -9
- data/lib/ably/models/nil_logger.rb +20 -0
- data/lib/ably/models/paginated_resource.rb +5 -2
- data/lib/ably/models/presence_message.rb +21 -12
- data/lib/ably/models/protocol_message.rb +22 -6
- data/lib/ably/modules/ably.rb +11 -0
- data/lib/ably/modules/async_wrapper.rb +2 -0
- data/lib/ably/modules/conversions.rb +23 -3
- data/lib/ably/modules/encodeable.rb +2 -1
- data/lib/ably/modules/enum.rb +2 -0
- data/lib/ably/modules/event_emitter.rb +7 -1
- data/lib/ably/modules/event_machine_helpers.rb +2 -0
- data/lib/ably/modules/http_helpers.rb +2 -0
- data/lib/ably/modules/model_common.rb +12 -2
- data/lib/ably/modules/state_emitter.rb +76 -0
- data/lib/ably/modules/state_machine.rb +53 -0
- data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/ably/realtime.rb +4 -2
- data/lib/ably/realtime/channel.rb +51 -58
- data/lib/ably/realtime/channel/channel_manager.rb +91 -0
- data/lib/ably/realtime/channel/channel_state_machine.rb +68 -0
- data/lib/ably/realtime/client.rb +70 -26
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +31 -13
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
- data/lib/ably/realtime/connection.rb +135 -92
- data/lib/ably/realtime/connection/connection_manager.rb +216 -33
- data/lib/ably/realtime/connection/connection_state_machine.rb +30 -73
- data/lib/ably/realtime/models/nil_channel.rb +10 -1
- data/lib/ably/realtime/presence.rb +336 -92
- data/lib/ably/rest.rb +2 -2
- data/lib/ably/rest/channel.rb +13 -4
- data/lib/ably/rest/client.rb +138 -38
- data/lib/ably/rest/middleware/logger.rb +24 -3
- data/lib/ably/rest/presence.rb +12 -7
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_history_spec.rb +101 -85
- data/spec/acceptance/realtime/channel_spec.rb +461 -120
- data/spec/acceptance/realtime/client_spec.rb +119 -0
- data/spec/acceptance/realtime/connection_failures_spec.rb +499 -0
- data/spec/acceptance/realtime/connection_spec.rb +571 -97
- data/spec/acceptance/realtime/message_spec.rb +347 -333
- data/spec/acceptance/realtime/presence_history_spec.rb +35 -40
- data/spec/acceptance/realtime/presence_spec.rb +769 -239
- data/spec/acceptance/realtime/stats_spec.rb +14 -22
- data/spec/acceptance/realtime/time_spec.rb +16 -20
- data/spec/acceptance/rest/auth_spec.rb +425 -364
- data/spec/acceptance/rest/base_spec.rb +108 -176
- data/spec/acceptance/rest/channel_spec.rb +89 -89
- data/spec/acceptance/rest/channels_spec.rb +30 -32
- data/spec/acceptance/rest/client_spec.rb +273 -0
- data/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/spec/acceptance/rest/message_spec.rb +186 -163
- data/spec/acceptance/rest/presence_spec.rb +150 -111
- data/spec/acceptance/rest/stats_spec.rb +45 -40
- data/spec/acceptance/rest/time_spec.rb +8 -10
- data/spec/rspec_config.rb +10 -1
- data/spec/shared/client_initializer_behaviour.rb +212 -0
- data/spec/{support/model_helper.rb → shared/model_behaviour.rb} +6 -6
- data/spec/{support/protocol_msgbus_helper.rb → shared/protocol_msgbus_behaviour.rb} +1 -1
- data/spec/spec_helper.rb +9 -0
- data/spec/support/api_helper.rb +11 -0
- data/spec/support/event_machine_helper.rb +101 -3
- data/spec/support/markdown_spec_formatter.rb +90 -0
- data/spec/support/private_api_formatter.rb +36 -0
- data/spec/support/protocol_helper.rb +32 -0
- data/spec/support/random_helper.rb +15 -0
- data/spec/support/test_app.rb +4 -0
- data/spec/unit/auth_spec.rb +68 -0
- data/spec/unit/logger_spec.rb +77 -66
- data/spec/unit/models/error_info_spec.rb +1 -1
- data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +2 -3
- data/spec/unit/models/message_encoders/base64_spec.rb +2 -2
- data/spec/unit/models/message_encoders/cipher_spec.rb +2 -2
- data/spec/unit/models/message_encoders/utf8_spec.rb +2 -46
- data/spec/unit/models/message_spec.rb +160 -15
- data/spec/unit/models/paginated_resource_spec.rb +29 -27
- data/spec/unit/models/presence_message_spec.rb +163 -20
- data/spec/unit/models/protocol_message_spec.rb +43 -8
- data/spec/unit/modules/async_wrapper_spec.rb +2 -3
- data/spec/unit/modules/conversions_spec.rb +1 -1
- data/spec/unit/modules/enum_spec.rb +2 -3
- data/spec/unit/modules/event_emitter_spec.rb +62 -5
- data/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/spec/unit/realtime/channel_spec.rb +107 -2
- data/spec/unit/realtime/channels_spec.rb +1 -0
- data/spec/unit/realtime/client_spec.rb +8 -48
- data/spec/unit/realtime/connection_spec.rb +3 -3
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +2 -2
- data/spec/unit/realtime/presence_spec.rb +13 -4
- data/spec/unit/realtime/realtime_spec.rb +0 -11
- data/spec/unit/realtime/websocket_transport_spec.rb +2 -2
- data/spec/unit/rest/channel_spec.rb +109 -0
- data/spec/unit/rest/channels_spec.rb +4 -3
- data/spec/unit/rest/client_spec.rb +30 -125
- data/spec/unit/rest/rest_spec.rb +10 -0
- data/spec/unit/util/crypto_spec.rb +10 -5
- data/spec/unit/util/pub_sub_spec.rb +5 -5
- metadata +44 -12
- data/spec/integration/modules/state_emitter_spec.rb +0 -80
- data/spec/integration/rest/auth.rb +0 -9
@@ -1,439 +1,453 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
require 'spec_helper'
|
2
|
-
require 'securerandom'
|
3
|
-
require 'json'
|
4
3
|
require 'base64'
|
4
|
+
require 'json'
|
5
|
+
require 'securerandom'
|
5
6
|
|
6
|
-
describe 'Ably::Realtime::Channel
|
7
|
-
|
7
|
+
describe 'Ably::Realtime::Channel Message', :event_machine do
|
8
|
+
vary_by_protocol do
|
9
|
+
let(:default_options) { options.merge(api_key: api_key, environment: environment, protocol: protocol) }
|
10
|
+
let(:client_options) { default_options }
|
11
|
+
let(:client) do
|
12
|
+
Ably::Realtime::Client.new(client_options)
|
13
|
+
end
|
14
|
+
let(:channel) { client.channel(channel_name) }
|
8
15
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
16
|
+
let(:other_client) do
|
17
|
+
Ably::Realtime::Client.new(client_options)
|
18
|
+
end
|
19
|
+
let(:other_client_channel) { other_client.channel(channel_name) }
|
20
|
+
|
21
|
+
let(:channel_name) { "subscribe_send_text-#{random_str}" }
|
22
|
+
let(:options) { { :protocol => :json } }
|
23
|
+
let(:payload) { 'Test message (subscribe_send_text)' }
|
24
|
+
|
25
|
+
it 'sends a String data payload' do
|
26
|
+
channel.attach
|
27
|
+
channel.on(:attached) do
|
28
|
+
channel.publish('test_event', payload) do |message|
|
29
|
+
expect(message.data).to eql(payload)
|
30
|
+
stop_reactor
|
31
|
+
end
|
14
32
|
end
|
15
|
-
|
33
|
+
end
|
16
34
|
|
17
|
-
|
18
|
-
|
35
|
+
context 'with ASCII_8BIT message name' do
|
36
|
+
let(:message_name) { random_str.encode(Encoding::ASCII_8BIT) }
|
37
|
+
it 'is converted into UTF_8' do
|
38
|
+
channel.attach do
|
39
|
+
channel.publish message_name, payload
|
40
|
+
end
|
41
|
+
channel.subscribe do |message|
|
42
|
+
expect(message.name.encoding).to eql(Encoding::UTF_8)
|
43
|
+
expect(message.name.encode(Encoding::ASCII_8BIT)).to eql(message_name)
|
44
|
+
stop_reactor
|
45
|
+
end
|
19
46
|
end
|
20
|
-
|
47
|
+
end
|
21
48
|
|
22
|
-
|
23
|
-
let(:
|
24
|
-
let(:
|
49
|
+
context 'when the message publisher has a client_id' do
|
50
|
+
let(:client_id) { random_str }
|
51
|
+
let(:client_options) { default_options.merge(client_id: client_id) }
|
25
52
|
|
26
|
-
it '
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
53
|
+
it 'contains a #client_id attribute' do
|
54
|
+
when_all(channel.attach, other_client_channel.attach) do
|
55
|
+
other_client_channel.subscribe('event') do |message|
|
56
|
+
expect(message.client_id).to eql(client_id)
|
57
|
+
stop_reactor
|
58
|
+
end
|
59
|
+
channel.publish('event', payload)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#connection_id attribute' do
|
65
|
+
context 'over realtime' do
|
66
|
+
it 'matches the sender connection#id' do
|
67
|
+
when_all(channel.attach, other_client_channel.attach) do
|
68
|
+
other_client_channel.subscribe('event') do |message|
|
69
|
+
expect(message.connection_id).to eql(client.connection.id)
|
32
70
|
stop_reactor
|
33
71
|
end
|
72
|
+
channel.publish('event', payload)
|
34
73
|
end
|
35
74
|
end
|
36
75
|
end
|
37
76
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
channel.
|
42
|
-
|
43
|
-
expect(message.data).to eql(payload)
|
77
|
+
context 'when retrieved over REST' do
|
78
|
+
it 'matches the sender connection#id' do
|
79
|
+
channel.publish('event', payload) do
|
80
|
+
channel.history do |messages|
|
81
|
+
expect(messages.first.connection_id).to eql(client.connection.id)
|
44
82
|
stop_reactor
|
45
83
|
end
|
46
84
|
end
|
47
85
|
end
|
48
86
|
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'local echo when published' do
|
90
|
+
it 'is enabled by default' do
|
91
|
+
channel.attach do
|
92
|
+
channel.publish 'test_event', payload
|
93
|
+
channel.subscribe('test_event') do |message|
|
94
|
+
expect(message.data).to eql(payload)
|
95
|
+
stop_reactor
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
49
99
|
|
50
|
-
context 'with echo_messages
|
100
|
+
context 'with :echo_messages option set to false' do
|
51
101
|
let(:no_echo_client) do
|
52
102
|
Ably::Realtime::Client.new(default_options.merge(echo_messages: false))
|
53
103
|
end
|
54
104
|
let(:no_echo_channel) { no_echo_client.channel(channel_name) }
|
55
105
|
|
56
|
-
it '
|
57
|
-
|
58
|
-
|
59
|
-
no_echo_channel.
|
60
|
-
no_echo_channel.publish 'test_event', payload
|
106
|
+
it 'will not echo messages to the client but will still broadcast messages to other connected clients', em_timeout: 10 do
|
107
|
+
channel.attach do |echo_channel|
|
108
|
+
no_echo_channel.attach do
|
109
|
+
no_echo_channel.publish 'test_event', payload
|
61
110
|
|
62
|
-
|
63
|
-
|
64
|
-
|
111
|
+
no_echo_channel.subscribe('test_event') do |message|
|
112
|
+
fail "Message should not have been echoed back"
|
113
|
+
end
|
65
114
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
115
|
+
echo_channel.subscribe('test_event') do |message|
|
116
|
+
expect(message.data).to eql(payload)
|
117
|
+
EventMachine.add_timer(1) do
|
118
|
+
stop_reactor
|
71
119
|
end
|
72
120
|
end
|
73
121
|
end
|
74
122
|
end
|
75
123
|
end
|
76
124
|
end
|
125
|
+
end
|
77
126
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
127
|
+
context 'publishing lots of messages across two connections' do
|
128
|
+
let(:send_count) { 30 }
|
129
|
+
let(:expected_echos) { send_count * 2 }
|
130
|
+
let(:channel_name) { random_str }
|
131
|
+
let(:echos) do
|
132
|
+
{ client: 0, other: 0 }
|
133
|
+
end
|
134
|
+
let(:callbacks) do
|
135
|
+
{ client: 0, other: 0 }
|
136
|
+
end
|
88
137
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
expect(echos[:other]).to eql(expected_echos)
|
138
|
+
it 'sends and receives the messages on both opened connections and calls the success callbacks for each message published', em_timeout: 10 do
|
139
|
+
check_message_and_callback_counts = Proc.new do
|
140
|
+
if echos[:client] == expected_echos && echos[:other] == expected_echos
|
141
|
+
# Wait for message backlog to clear
|
142
|
+
EventMachine.add_timer(0.5) do
|
143
|
+
expect(echos[:client]).to eql(expected_echos)
|
144
|
+
expect(echos[:other]).to eql(expected_echos)
|
97
145
|
|
98
|
-
|
99
|
-
|
146
|
+
expect(callbacks[:client]).to eql(send_count)
|
147
|
+
expect(callbacks[:other]).to eql(send_count)
|
100
148
|
|
101
|
-
|
102
|
-
end
|
103
|
-
end
|
149
|
+
stop_reactor
|
104
150
|
end
|
151
|
+
end
|
152
|
+
end
|
105
153
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
115
|
-
other_client_channel.publish('test_event', "#{index}: #{payload}") do
|
116
|
-
callbacks[:other] += 1
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
published = true
|
121
|
-
end
|
122
|
-
end
|
154
|
+
channel.subscribe('test_event') do |message|
|
155
|
+
echos[:client] += 1
|
156
|
+
check_message_and_callback_counts.call
|
157
|
+
end
|
158
|
+
other_client_channel.subscribe('test_event') do |message|
|
159
|
+
echos[:other] += 1
|
160
|
+
check_message_and_callback_counts.call
|
161
|
+
end
|
123
162
|
|
124
|
-
|
125
|
-
|
126
|
-
|
163
|
+
when_all(channel.attach, other_client_channel.attach) do
|
164
|
+
send_count.times do |index|
|
165
|
+
channel.publish('test_event', "#{index}: #{payload}") do
|
166
|
+
callbacks[:client] += 1
|
127
167
|
end
|
128
|
-
other_client_channel.
|
129
|
-
|
130
|
-
check_message_and_callback_counts.call
|
168
|
+
other_client_channel.publish('test_event', "#{index}: #{payload}") do
|
169
|
+
callbacks[:other] += 1
|
131
170
|
end
|
132
|
-
|
133
|
-
channel.attach &attach_callback
|
134
|
-
other_client_channel.attach &attach_callback
|
135
171
|
end
|
136
172
|
end
|
137
173
|
end
|
174
|
+
end
|
138
175
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
deferrable.callback do |message|
|
157
|
-
fail 'Success callback should not have been called'
|
158
|
-
stop_reactor
|
159
|
-
end
|
160
|
-
end
|
176
|
+
context 'without suitable publishing permissions' do
|
177
|
+
let(:restricted_client) do
|
178
|
+
Ably::Realtime::Client.new(options.merge(api_key: restricted_api_key, environment: environment, protocol: protocol))
|
179
|
+
end
|
180
|
+
let(:restricted_channel) { restricted_client.channel("cansubscribe:example") }
|
181
|
+
let(:payload) { 'Test message without permission to publish' }
|
182
|
+
|
183
|
+
it 'calls the error callback' do
|
184
|
+
restricted_channel.attach do
|
185
|
+
deferrable = restricted_channel.publish('test_event', payload)
|
186
|
+
deferrable.errback do |message, error|
|
187
|
+
expect(message.data).to eql(payload)
|
188
|
+
expect(error.status).to eql(401)
|
189
|
+
stop_reactor
|
190
|
+
end
|
191
|
+
deferrable.callback do |message|
|
192
|
+
fail 'Success callback should not have been called'
|
161
193
|
end
|
162
194
|
end
|
163
195
|
end
|
196
|
+
end
|
164
197
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
end
|
198
|
+
context 'encoding and decoding encrypted messages' do
|
199
|
+
shared_examples 'an Ably encrypter and decrypter' do |item, data|
|
200
|
+
let(:algorithm) { data['algorithm'].upcase }
|
201
|
+
let(:mode) { data['mode'].upcase }
|
202
|
+
let(:key_length) { data['keylength'] }
|
203
|
+
let(:secret_key) { Base64.decode64(data['key']) }
|
204
|
+
let(:iv) { Base64.decode64(data['iv']) }
|
205
|
+
|
206
|
+
let(:cipher_options) { { key: secret_key, iv: iv, algorithm: algorithm, mode: mode, key_length: key_length } }
|
207
|
+
|
208
|
+
context 'with #publish and #subscribe' do
|
209
|
+
let(:encoded) { item['encoded'] }
|
210
|
+
let(:encoded_data) { encoded['data'] }
|
211
|
+
let(:encoded_encoding) { encoded['encoding'] }
|
212
|
+
let(:encoded_data_decoded) do
|
213
|
+
if encoded_encoding == 'json'
|
214
|
+
JSON.parse(encoded_data)
|
215
|
+
elsif encoded_encoding == 'base64'
|
216
|
+
Base64.decode64(encoded_data)
|
217
|
+
else
|
218
|
+
encoded_data
|
187
219
|
end
|
220
|
+
end
|
188
221
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
end
|
222
|
+
let(:encrypted) { item['encrypted'] }
|
223
|
+
let(:encrypted_data) { encrypted['data'] }
|
224
|
+
let(:encrypted_encoding) { encrypted['encoding'] }
|
225
|
+
let(:encrypted_data_decoded) do
|
226
|
+
if encrypted_encoding.match(%r{/base64$})
|
227
|
+
Base64.decode64(encrypted_data)
|
228
|
+
else
|
229
|
+
encrypted_data
|
198
230
|
end
|
231
|
+
end
|
199
232
|
|
200
|
-
|
201
|
-
|
202
|
-
it 'encrypts message automatically when published' do
|
203
|
-
run_reactor do
|
204
|
-
encrypted_channel.__incoming_msgbus__.unsubscribe # remove all subscribe callbacks that could decrypt the message
|
205
|
-
|
206
|
-
encrypted_channel.__incoming_msgbus__.subscribe(:message) do |message|
|
207
|
-
if protocol == :json
|
208
|
-
expect(message['encoding']).to eql(encrypted_encoding)
|
209
|
-
expect(message['data']).to eql(encrypted_data)
|
210
|
-
else
|
211
|
-
# Messages received over binary protocol will not have Base64 encoded data
|
212
|
-
expect(message['encoding']).to eql(encrypted_encoding.gsub(%r{/base64$}, ''))
|
213
|
-
expect(message['data']).to eql(encrypted_data_decoded)
|
214
|
-
end
|
215
|
-
stop_reactor
|
216
|
-
end
|
233
|
+
let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
217
234
|
|
218
|
-
|
219
|
-
|
220
|
-
end
|
235
|
+
it 'encrypts message automatically before they are pushed to the server' do
|
236
|
+
encrypted_channel.__incoming_msgbus__.unsubscribe # remove all subscribe callbacks that could decrypt the message
|
221
237
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
238
|
+
encrypted_channel.__incoming_msgbus__.subscribe(:message) do |message|
|
239
|
+
if protocol == :json
|
240
|
+
expect(message['encoding']).to eql(encrypted_encoding)
|
241
|
+
expect(message['data']).to eql(encrypted_data)
|
242
|
+
else
|
243
|
+
# Messages received over binary protocol will not have Base64 encoded data
|
244
|
+
expect(message['encoding']).to eql(encrypted_encoding.gsub(%r{/base64$}, ''))
|
245
|
+
expect(message['data']).to eql(encrypted_data_decoded)
|
230
246
|
end
|
247
|
+
stop_reactor
|
231
248
|
end
|
232
|
-
end
|
233
|
-
end
|
234
249
|
|
235
|
-
|
250
|
+
encrypted_channel.publish 'example', encoded_data_decoded
|
251
|
+
end
|
236
252
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
253
|
+
it 'sends and receives messages that are encrypted & decrypted by the Ably library' do
|
254
|
+
encrypted_channel.publish 'example', encoded_data_decoded
|
255
|
+
encrypted_channel.subscribe do |message|
|
256
|
+
expect(message.data).to eql(encoded_data_decoded)
|
257
|
+
expect(message.encoding).to be_nil
|
258
|
+
stop_reactor
|
241
259
|
end
|
242
260
|
end
|
243
261
|
end
|
262
|
+
end
|
244
263
|
|
245
|
-
|
246
|
-
data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-128.json')))
|
247
|
-
add_tests_for_data data
|
248
|
-
end
|
264
|
+
resources_root = File.expand_path('../../../resources', __FILE__)
|
249
265
|
|
250
|
-
|
251
|
-
|
252
|
-
|
266
|
+
def self.add_tests_for_data(data)
|
267
|
+
data['items'].each_with_index do |item, index|
|
268
|
+
context "item #{index} with encrypted encoding #{item['encrypted']['encoding']}" do
|
269
|
+
it_behaves_like 'an Ably encrypter and decrypter', item, data
|
270
|
+
end
|
253
271
|
end
|
272
|
+
end
|
254
273
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
let(:data) { MessagePack.pack({ 'key' => SecureRandom.hex }) }
|
261
|
-
let(:message_count) { 50 }
|
262
|
-
|
263
|
-
it 'encrypt and decrypt messages' do
|
264
|
-
messages_received = {
|
265
|
-
decrypted: 0,
|
266
|
-
encrypted: 0
|
267
|
-
}
|
268
|
-
|
269
|
-
run_reactor do
|
270
|
-
encrypted_channel_client2.attach do
|
271
|
-
encrypted_channel_client2.subscribe do |message|
|
272
|
-
expect(message.data).to eql("#{message.name}-#{data}")
|
273
|
-
expect(message.encoding).to be_nil
|
274
|
-
messages_received[:decrypted] += 1
|
275
|
-
stop_reactor if messages_received[:decrypted] == message_count
|
276
|
-
end
|
274
|
+
context 'with AES-128-CBC using crypto-data-128.json fixtures' do
|
275
|
+
data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-128.json')))
|
276
|
+
add_tests_for_data data
|
277
|
+
end
|
277
278
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
end
|
279
|
+
context 'with AES-256-CBC using crypto-data-256.json fixtures' do
|
280
|
+
data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-256.json')))
|
281
|
+
add_tests_for_data data
|
282
|
+
end
|
283
283
|
|
284
|
-
|
285
|
-
|
286
|
-
|
284
|
+
context 'with multiple sends from one client to another' do
|
285
|
+
let(:cipher_options) { { key: random_str(32) } }
|
286
|
+
let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
287
|
+
let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
288
|
+
|
289
|
+
let(:data) { MessagePack.pack({ 'key' => random_str }) }
|
290
|
+
let(:message_count) { 50 }
|
291
|
+
|
292
|
+
it 'encrypts and decrypts all messages' do
|
293
|
+
messages_received = {
|
294
|
+
decrypted: 0,
|
295
|
+
encrypted: 0
|
296
|
+
}
|
297
|
+
|
298
|
+
encrypted_channel_client2.attach do
|
299
|
+
encrypted_channel_client2.subscribe do |message|
|
300
|
+
expect(message.data).to eql("#{message.name}-#{data}")
|
301
|
+
expect(message.encoding).to be_nil
|
302
|
+
messages_received[:decrypted] += 1
|
303
|
+
stop_reactor if messages_received[:decrypted] == message_count
|
304
|
+
end
|
305
|
+
|
306
|
+
encrypted_channel_client1.__incoming_msgbus__.subscribe(:message) do |message|
|
307
|
+
expect(message['encoding']).to match(/cipher\+/)
|
308
|
+
messages_received[:encrypted] += 1
|
287
309
|
end
|
288
310
|
end
|
289
|
-
end
|
290
311
|
|
291
|
-
|
292
|
-
|
293
|
-
let(:other_client) do
|
294
|
-
Ably::Realtime::Client.new(default_options.merge(protocol: other_protocol))
|
312
|
+
message_count.times do |index|
|
313
|
+
encrypted_channel_client2.publish index.to_s, "#{index}-#{data}"
|
295
314
|
end
|
315
|
+
end
|
316
|
+
end
|
296
317
|
|
297
|
-
|
298
|
-
|
299
|
-
|
318
|
+
context 'subscribing with a different transport protocol' do
|
319
|
+
let(:other_protocol) { protocol == :msgpack ? :json : :msgpack }
|
320
|
+
let(:other_client) do
|
321
|
+
Ably::Realtime::Client.new(default_options.merge(protocol: other_protocol))
|
322
|
+
end
|
300
323
|
|
301
|
-
|
302
|
-
|
303
|
-
|
324
|
+
let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
325
|
+
let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
326
|
+
let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
304
327
|
|
305
|
-
|
306
|
-
|
328
|
+
before do
|
329
|
+
expect(other_client.protocol_binary?).to_not eql(client.protocol_binary?)
|
330
|
+
end
|
307
331
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
332
|
+
[MessagePack.pack({ 'key' => SecureRandom.hex }), 'ã unicode', { 'key' => SecureRandom.hex }].each do |payload|
|
333
|
+
payload_description = "#{payload.class}#{" #{payload.encoding}" if payload.kind_of?(String)}"
|
334
|
+
|
335
|
+
it "delivers a #{payload_description} payload to the receiver" do
|
336
|
+
encrypted_channel_client1.publish 'example', payload
|
337
|
+
encrypted_channel_client2.subscribe do |message|
|
338
|
+
expect(message.data).to eql(payload)
|
339
|
+
expect(message.encoding).to be_nil
|
340
|
+
stop_reactor
|
317
341
|
end
|
318
342
|
end
|
319
343
|
end
|
344
|
+
end
|
320
345
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
346
|
+
context 'publishing on an unencrypted channel and subscribing on an encrypted channel with another client' do
|
347
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
348
|
+
let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
349
|
+
let(:unencrypted_channel_client1) { client.channel(channel_name) }
|
350
|
+
let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
325
351
|
|
326
|
-
|
352
|
+
let(:payload) { MessagePack.pack({ 'key' => random_str }) }
|
327
353
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
stop_reactor
|
335
|
-
end
|
336
|
-
end
|
354
|
+
it 'does not attempt to decrypt the message' do
|
355
|
+
unencrypted_channel_client1.publish 'example', payload
|
356
|
+
encrypted_channel_client2.subscribe do |message|
|
357
|
+
expect(message.data).to eql(payload)
|
358
|
+
expect(message.encoding).to be_nil
|
359
|
+
stop_reactor
|
337
360
|
end
|
338
361
|
end
|
362
|
+
end
|
339
363
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
364
|
+
context 'publishing on an encrypted channel and subscribing on an unencrypted channel with another client' do
|
365
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
366
|
+
let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
367
|
+
let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
368
|
+
let(:unencrypted_channel_client2) { other_client.channel(channel_name) }
|
344
369
|
|
345
|
-
|
370
|
+
let(:payload) { MessagePack.pack({ 'key' => random_str }) }
|
346
371
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
stop_reactor
|
354
|
-
end
|
355
|
-
end
|
372
|
+
it 'delivers the message but still encrypted with a value in the #encoding attribute' do
|
373
|
+
encrypted_channel_client1.publish 'example', payload
|
374
|
+
unencrypted_channel_client2.subscribe do |message|
|
375
|
+
expect(message.data).to_not eql(payload)
|
376
|
+
expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
|
377
|
+
stop_reactor
|
356
378
|
end
|
379
|
+
end
|
357
380
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
stop_reactor
|
367
|
-
end
|
368
|
-
end
|
381
|
+
it 'triggers a Cipher error on the channel' do
|
382
|
+
unencrypted_channel_client2.attach do
|
383
|
+
encrypted_channel_client1.publish 'example', payload
|
384
|
+
unencrypted_channel_client2.on(:error) do |error|
|
385
|
+
expect(error).to be_a(Ably::Exceptions::CipherError)
|
386
|
+
expect(error.code).to eql(92001)
|
387
|
+
expect(error.message).to match(/Message cannot be decrypted/)
|
388
|
+
stop_reactor
|
369
389
|
end
|
370
390
|
end
|
371
391
|
end
|
392
|
+
end
|
372
393
|
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
end
|
389
|
-
end
|
394
|
+
context 'publishing on an encrypted channel and subscribing with a different algorithm on another client' do
|
395
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
396
|
+
let(:cipher_options_client1) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
397
|
+
let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
|
398
|
+
let(:cipher_options_client2) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
|
399
|
+
let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
|
400
|
+
|
401
|
+
let(:payload) { MessagePack.pack({ 'key' => random_str }) }
|
402
|
+
|
403
|
+
it 'delivers the message but still encrypted with the cipher detials in the #encoding attribute' do
|
404
|
+
encrypted_channel_client1.publish 'example', payload
|
405
|
+
encrypted_channel_client2.subscribe do |message|
|
406
|
+
expect(message.data).to_not eql(payload)
|
407
|
+
expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
|
408
|
+
stop_reactor
|
390
409
|
end
|
410
|
+
end
|
391
411
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
stop_reactor
|
401
|
-
end
|
402
|
-
end
|
412
|
+
it 'triggers a Cipher error on the channel' do
|
413
|
+
encrypted_channel_client2.attach do
|
414
|
+
encrypted_channel_client1.publish 'example', payload
|
415
|
+
encrypted_channel_client2.on(:error) do |error|
|
416
|
+
expect(error).to be_a(Ably::Exceptions::CipherError)
|
417
|
+
expect(error.code).to eql(92002)
|
418
|
+
expect(error.message).to match(/Cipher algorithm [\w-]+ does not match/)
|
419
|
+
stop_reactor
|
403
420
|
end
|
404
421
|
end
|
405
422
|
end
|
423
|
+
end
|
406
424
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
end
|
423
|
-
end
|
425
|
+
context 'publishing on an encrypted channel and subscribing with a different key on another client' do
|
426
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
427
|
+
let(:cipher_options_client1) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
428
|
+
let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
|
429
|
+
let(:cipher_options_client2) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
430
|
+
let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
|
431
|
+
|
432
|
+
let(:payload) { MessagePack.pack({ 'key' => random_str }) }
|
433
|
+
|
434
|
+
it 'delivers the message but still encrypted with the cipher details in the #encoding attribute' do
|
435
|
+
encrypted_channel_client1.publish 'example', payload
|
436
|
+
encrypted_channel_client2.subscribe do |message|
|
437
|
+
expect(message.data).to_not eql(payload)
|
438
|
+
expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
|
439
|
+
stop_reactor
|
424
440
|
end
|
441
|
+
end
|
425
442
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
stop_reactor
|
435
|
-
end
|
436
|
-
end
|
443
|
+
it 'triggers a Cipher error on the channel' do
|
444
|
+
encrypted_channel_client2.attach do
|
445
|
+
encrypted_channel_client1.publish 'example', payload
|
446
|
+
encrypted_channel_client2.on(:error) do |error|
|
447
|
+
expect(error).to be_a(Ably::Exceptions::CipherError)
|
448
|
+
expect(error.code).to eql(92003)
|
449
|
+
expect(error.message).to match(/CipherError decrypting data/)
|
450
|
+
stop_reactor
|
437
451
|
end
|
438
452
|
end
|
439
453
|
end
|