mqtt 0.1.0 → 0.2.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.
- data/{LICENSE → LICENSE.md} +0 -0
- data/{NEWS → NEWS.md} +15 -0
- data/{README → README.md} +3 -2
- data/lib/mqtt.rb +10 -1
- data/lib/mqtt/client.rb +192 -40
- data/lib/mqtt/packet.rb +114 -34
- data/lib/mqtt/proxy.rb +12 -2
- data/lib/mqtt/version.rb +1 -1
- data/spec/mqtt_client_spec.rb +355 -175
- data/spec/mqtt_packet_spec.rb +241 -273
- data/spec/{zz_integration_spec.rb → zz_client_integration_spec.rb} +0 -0
- metadata +90 -107
data/lib/mqtt/proxy.rb
CHANGED
@@ -1,11 +1,21 @@
|
|
1
1
|
# Class for implementing a proxy to filter/mangle MQTT packets.
|
2
2
|
class MQTT::Proxy
|
3
|
+
# Address to bind listening socket to
|
3
4
|
attr_reader :local_host
|
5
|
+
|
6
|
+
# Port to bind listening socket to
|
4
7
|
attr_reader :local_port
|
8
|
+
|
9
|
+
# Address of upstream broker to send packets upstream to
|
5
10
|
attr_reader :broker_host
|
11
|
+
|
12
|
+
# Port of upstream broker to send packets upstream to.
|
6
13
|
attr_reader :broker_port
|
7
|
-
|
14
|
+
|
15
|
+
# Time in seconds before disconnecting an idle connection
|
8
16
|
attr_reader :select_timeout
|
17
|
+
|
18
|
+
# Ruby Logger object to send informational messages to
|
9
19
|
attr_reader :logger
|
10
20
|
|
11
21
|
# Create a new MQTT Proxy instance.
|
@@ -23,7 +33,7 @@ class MQTT::Proxy
|
|
23
33
|
def initialize(args={})
|
24
34
|
@local_host = args[:local_host] || '0.0.0.0'
|
25
35
|
@local_port = args[:local_port] || MQTT::DEFAULT_PORT
|
26
|
-
@broker_host = args[:broker_host]
|
36
|
+
@broker_host = args[:broker_host]
|
27
37
|
@broker_port = args[:broker_port] || 18830
|
28
38
|
@select_timeout = args[:select_timeout] || 60
|
29
39
|
|
data/lib/mqtt/version.rb
CHANGED
data/spec/mqtt_client_spec.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
# Encoding is set to binary, so that the binary packets aren't validated as UTF-8
|
3
|
+
|
1
4
|
$:.unshift(File.dirname(__FILE__))
|
2
5
|
|
3
6
|
require 'spec_helper'
|
@@ -6,111 +9,236 @@ require 'mqtt'
|
|
6
9
|
describe MQTT::Client do
|
7
10
|
|
8
11
|
before(:each) do
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
+
# Reset environment variable
|
13
|
+
ENV.delete('MQTT_BROKER')
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:client) { MQTT::Client.new(:remote_host => 'localhost') }
|
17
|
+
let(:socket) do
|
18
|
+
socket = StringIO.new
|
19
|
+
if socket.respond_to?(:set_encoding)
|
20
|
+
socket.set_encoding("binary")
|
21
|
+
else
|
22
|
+
socket
|
23
|
+
end
|
12
24
|
end
|
13
25
|
|
14
26
|
describe "initializing a client" do
|
15
27
|
it "with no arguments, it should use the defaults" do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
28
|
+
client = MQTT::Client.new
|
29
|
+
client.remote_host.should == nil
|
30
|
+
client.remote_port.should == 1883
|
31
|
+
client.keep_alive.should == 15
|
20
32
|
end
|
21
33
|
|
22
34
|
it "with a single string argument, it should use it has the host" do
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
35
|
+
client = MQTT::Client.new('otherhost.mqtt.org')
|
36
|
+
client.remote_host.should == 'otherhost.mqtt.org'
|
37
|
+
client.remote_port.should == 1883
|
38
|
+
client.keep_alive.should == 15
|
27
39
|
end
|
28
40
|
|
29
41
|
it "with two arguments, it should use it as the host and port" do
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
42
|
+
client = MQTT::Client.new('otherhost.mqtt.org', 1000)
|
43
|
+
client.remote_host.should == 'otherhost.mqtt.org'
|
44
|
+
client.remote_port.should == 1000
|
45
|
+
client.keep_alive.should == 15
|
34
46
|
end
|
35
47
|
|
36
48
|
it "with names arguments, it should use those as arguments" do
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
49
|
+
client = MQTT::Client.new(:remote_host => 'otherhost.mqtt.org', :remote_port => 1000)
|
50
|
+
client.remote_host.should == 'otherhost.mqtt.org'
|
51
|
+
client.remote_port.should == 1000
|
52
|
+
client.keep_alive.should == 15
|
41
53
|
end
|
42
54
|
|
43
55
|
it "with a hash, it should use those as arguments" do
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
56
|
+
client = MQTT::Client.new({:remote_host => 'otherhost.mqtt.org', :remote_port => 1000})
|
57
|
+
client.remote_host.should == 'otherhost.mqtt.org'
|
58
|
+
client.remote_port.should == 1000
|
59
|
+
client.keep_alive.should == 15
|
48
60
|
end
|
49
61
|
|
50
62
|
it "with a hash containing just a keep alive setting" do
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
63
|
+
client = MQTT::Client.new(:remote_host => 'localhost', :keep_alive => 60)
|
64
|
+
client.remote_host.should == 'localhost'
|
65
|
+
client.remote_port.should == 1883
|
66
|
+
client.keep_alive.should == 60
|
67
|
+
end
|
68
|
+
|
69
|
+
it "with a combination of a host name and a hash of settings" do
|
70
|
+
client = MQTT::Client.new('localhost', :keep_alive => 65)
|
71
|
+
client.remote_host.should == 'localhost'
|
72
|
+
client.remote_port.should == 1883
|
73
|
+
client.keep_alive.should == 65
|
74
|
+
end
|
75
|
+
|
76
|
+
it "with a combination of a host name, port and a hash of settings" do
|
77
|
+
client = MQTT::Client.new('localhost', 1888, :keep_alive => 65)
|
78
|
+
client.remote_host.should == 'localhost'
|
79
|
+
client.remote_port.should == 1888
|
80
|
+
client.keep_alive.should == 65
|
81
|
+
end
|
82
|
+
|
83
|
+
it "with a mqtt:// URI containing just a hostname" do
|
84
|
+
client = MQTT::Client.new(URI.parse('mqtt://mqtt.example.com'))
|
85
|
+
client.remote_host.should == 'mqtt.example.com'
|
86
|
+
client.remote_port.should == 1883
|
87
|
+
client.ssl.should be_false
|
88
|
+
end
|
89
|
+
|
90
|
+
it "with a mqtts:// URI containing just a hostname" do
|
91
|
+
client = MQTT::Client.new(URI.parse('mqtts://mqtt.example.com'))
|
92
|
+
client.remote_host.should == 'mqtt.example.com'
|
93
|
+
client.remote_port.should == 8883
|
94
|
+
client.ssl.should be_true
|
95
|
+
end
|
96
|
+
|
97
|
+
it "with a mqtt:// URI containing a custom port number" do
|
98
|
+
client = MQTT::Client.new(URI.parse('mqtt://mqtt.example.com:1234/'))
|
99
|
+
client.remote_host.should == 'mqtt.example.com'
|
100
|
+
client.remote_port.should == 1234
|
101
|
+
client.ssl.should be_false
|
102
|
+
end
|
103
|
+
|
104
|
+
it "with a mqtts:// URI containing a custom port number" do
|
105
|
+
client = MQTT::Client.new(URI.parse('mqtts://mqtt.example.com:1234/'))
|
106
|
+
client.remote_host.should == 'mqtt.example.com'
|
107
|
+
client.remote_port.should == 1234
|
108
|
+
client.ssl.should be_true
|
109
|
+
end
|
110
|
+
|
111
|
+
it "with a URI containing a username and password" do
|
112
|
+
client = MQTT::Client.new(URI.parse('mqtt://auser:bpass@mqtt.example.com'))
|
113
|
+
client.remote_host.should == 'mqtt.example.com'
|
114
|
+
client.remote_port.should == 1883
|
115
|
+
client.username.should == 'auser'
|
116
|
+
client.password.should == 'bpass'
|
117
|
+
end
|
118
|
+
|
119
|
+
it "with a URI as a string" do
|
120
|
+
client = MQTT::Client.new('mqtt://mqtt.example.com')
|
121
|
+
client.remote_host.should == 'mqtt.example.com'
|
122
|
+
client.remote_port.should == 1883
|
123
|
+
end
|
124
|
+
|
125
|
+
it "with a URI and a hash of settings" do
|
126
|
+
client = MQTT::Client.new('mqtt://mqtt.example.com', :keep_alive => 65)
|
127
|
+
client.remote_host.should == 'mqtt.example.com'
|
128
|
+
client.remote_port.should == 1883
|
129
|
+
client.keep_alive.should == 65
|
130
|
+
end
|
131
|
+
|
132
|
+
it "with no arguments uses the MQTT_BROKER environment variable as connect URI" do
|
133
|
+
ENV['MQTT_BROKER'] = 'mqtt://mqtt.example.com:1234'
|
134
|
+
client = MQTT::Client.new
|
135
|
+
client.remote_host.should == 'mqtt.example.com'
|
136
|
+
client.remote_port.should == 1234
|
137
|
+
end
|
138
|
+
|
139
|
+
it "with an unsupported URI scheme" do
|
140
|
+
lambda {
|
141
|
+
client = MQTT::Client.new(URI.parse('http://mqtt.example.com/'))
|
142
|
+
}.should raise_error(
|
143
|
+
'Only the mqtt:// and mqtts:// schemes are supported'
|
144
|
+
)
|
55
145
|
end
|
56
146
|
|
57
147
|
it "with three arguments" do
|
58
148
|
lambda {
|
59
|
-
|
149
|
+
client = MQTT::Client.new(1, 2, 3)
|
60
150
|
}.should raise_error(
|
61
151
|
'Unsupported number of arguments'
|
62
152
|
)
|
63
153
|
end
|
64
154
|
end
|
65
155
|
|
156
|
+
describe "setting a client certificate file path" do
|
157
|
+
it "should add a certificate to the SSL context" do
|
158
|
+
client.ssl_context.cert.should be_nil
|
159
|
+
client.cert_file = fixture_path('client.pem')
|
160
|
+
client.ssl_context.cert.should be_a(OpenSSL::X509::Certificate)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe "setting a client private key file path" do
|
165
|
+
it "should add a certificate to the SSL context" do
|
166
|
+
client.ssl_context.key.should be_nil
|
167
|
+
client.key_file = fixture_path('client.key')
|
168
|
+
client.ssl_context.key.should be_a(OpenSSL::PKey::RSA)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "setting a Certificate Authority file path" do
|
173
|
+
it "should add a CA file path to the SSL context" do
|
174
|
+
client.ssl_context.ca_file.should be_nil
|
175
|
+
client.ca_file = fixture_path('root-ca.pem')
|
176
|
+
client.ssl_context.ca_file.should == fixture_path('root-ca.pem')
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should enable peer verification" do
|
180
|
+
client.ca_file = fixture_path('root-ca.pem')
|
181
|
+
client.ssl_context.verify_mode.should == OpenSSL::SSL::VERIFY_PEER
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
66
185
|
describe "when calling the 'connect' method on a client" do
|
67
186
|
before(:each) do
|
68
|
-
TCPSocket.stub(:new).and_return(
|
187
|
+
TCPSocket.stub(:new).and_return(socket)
|
69
188
|
Thread.stub(:new)
|
70
|
-
|
189
|
+
client.stub(:receive_connack)
|
71
190
|
end
|
72
191
|
|
73
192
|
it "should create a TCP Socket if not connected" do
|
74
|
-
TCPSocket.should_receive(:new).once.and_return(
|
75
|
-
|
193
|
+
TCPSocket.should_receive(:new).once.and_return(socket)
|
194
|
+
client.connect('myclient')
|
76
195
|
end
|
77
196
|
|
78
197
|
it "should not create a new TCP Socket if connected" do
|
79
|
-
|
198
|
+
client.stub(:connected?).and_return(true)
|
80
199
|
TCPSocket.should_receive(:new).never
|
81
|
-
|
200
|
+
client.connect('myclient')
|
82
201
|
end
|
83
202
|
|
84
203
|
it "should start the reader thread if not connected" do
|
85
204
|
Thread.should_receive(:new).once
|
86
|
-
|
205
|
+
client.connect('myclient')
|
87
206
|
end
|
88
207
|
|
89
208
|
it "should write a valid CONNECT packet to the socket if not connected" do
|
90
|
-
|
91
|
-
|
209
|
+
client.connect('myclient')
|
210
|
+
socket.string.should == "\020\026\x00\x06MQIsdp\x03\x02\x00\x0f\x00\x08myclient"
|
92
211
|
end
|
93
212
|
|
94
213
|
it "should try and read an acknowledgement packet to the socket if not connected" do
|
95
|
-
|
96
|
-
|
214
|
+
client.should_receive(:receive_connack).once
|
215
|
+
client.connect('myclient')
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should throw an exception if no host is configured" do
|
219
|
+
lambda {
|
220
|
+
client = MQTT::Client.new
|
221
|
+
client.connect
|
222
|
+
}.should raise_error(
|
223
|
+
'No MQTT broker host set when attempting to connect'
|
224
|
+
)
|
97
225
|
end
|
98
226
|
|
99
227
|
it "should disconnect after connecting, if a block is given" do
|
100
|
-
|
101
|
-
|
228
|
+
client.should_receive(:disconnect).once
|
229
|
+
client.connect('myclient') { nil }
|
102
230
|
end
|
103
231
|
|
104
232
|
it "should not disconnect after connecting, if no block is given" do
|
105
|
-
|
106
|
-
|
233
|
+
client.should_receive(:disconnect).never
|
234
|
+
client.connect('myclient')
|
107
235
|
end
|
108
236
|
|
109
237
|
it "should include the username and password for an authenticated connection" do
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
238
|
+
client.username = 'username'
|
239
|
+
client.password = 'password'
|
240
|
+
client.connect('myclient')
|
241
|
+
socket.string.should ==
|
114
242
|
"\x10\x2A"+
|
115
243
|
"\x00\x06MQIsdp"+
|
116
244
|
"\x03\xC2\x00\x0f"+
|
@@ -119,37 +247,89 @@ describe MQTT::Client do
|
|
119
247
|
"\x00\x08password"
|
120
248
|
end
|
121
249
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
250
|
+
context "no client id is given" do
|
251
|
+
it "should throw an exception if the clean session flag is false" do
|
252
|
+
lambda {
|
253
|
+
client.client_id = nil
|
254
|
+
client.clean_session = false
|
255
|
+
client.connect
|
256
|
+
}.should raise_error(
|
257
|
+
'Must provide a client_id if clean_session is set to false'
|
258
|
+
)
|
259
|
+
end
|
260
|
+
|
261
|
+
it "should generate a client if the clean session flag is true" do
|
262
|
+
client.client_id = nil
|
263
|
+
client.clean_session = true
|
264
|
+
client.connect
|
265
|
+
client.client_id.should match(/^\w+$/)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
context "and using ssl" do
|
270
|
+
let(:ssl_socket) {
|
271
|
+
double(
|
272
|
+
"SSLSocket",
|
273
|
+
:sync_close= => true,
|
274
|
+
:write => true,
|
275
|
+
:connect => true,
|
276
|
+
:closed? => false
|
277
|
+
)
|
278
|
+
}
|
279
|
+
|
280
|
+
it "should use ssl if it enabled using the :ssl => true parameter" do
|
281
|
+
OpenSSL::SSL::SSLSocket.should_receive(:new).and_return(ssl_socket)
|
282
|
+
ssl_socket.should_receive(:connect)
|
283
|
+
|
284
|
+
client = MQTT::Client.new('mqtt.example.com', :ssl => true)
|
285
|
+
client.stub(:receive_connack)
|
286
|
+
client.connect
|
287
|
+
end
|
288
|
+
|
289
|
+
it "should use ssl if it enabled using the mqtts:// scheme" do
|
290
|
+
OpenSSL::SSL::SSLSocket.should_receive(:new).and_return(ssl_socket)
|
291
|
+
ssl_socket.should_receive(:connect)
|
292
|
+
|
293
|
+
client = MQTT::Client.new('mqtts://mqtt.example.com')
|
294
|
+
client.stub(:receive_connack)
|
295
|
+
client.connect
|
296
|
+
end
|
297
|
+
|
298
|
+
it "should use set the SSL version, if the :ssl parameter is a symbol" do
|
299
|
+
OpenSSL::SSL::SSLSocket.should_receive(:new).and_return(ssl_socket)
|
300
|
+
ssl_socket.should_receive(:connect)
|
301
|
+
|
302
|
+
client = MQTT::Client.new('mqtt.example.com', :ssl => :TLSv1)
|
303
|
+
client.ssl_context.should_receive('ssl_version=').with(:TLSv1)
|
304
|
+
client.stub(:receive_connack)
|
305
|
+
client.connect
|
306
|
+
end
|
127
307
|
end
|
128
308
|
|
129
309
|
context "with a last will and testament set" do
|
130
310
|
before(:each) do
|
131
|
-
|
311
|
+
client.set_will('topic', 'hello', retain=false, qos=1)
|
132
312
|
end
|
133
313
|
|
134
314
|
it "should have set the Will's topic" do
|
135
|
-
|
315
|
+
client.will_topic.should == 'topic'
|
136
316
|
end
|
137
317
|
|
138
318
|
it "should have set the Will's payload" do
|
139
|
-
|
319
|
+
client.will_payload.should == 'hello'
|
140
320
|
end
|
141
321
|
|
142
322
|
it "should have set the Will's retain flag to true" do
|
143
|
-
|
323
|
+
client.will_retain.should be_false
|
144
324
|
end
|
145
325
|
|
146
326
|
it "should have set the Will's retain QOS value to 1" do
|
147
|
-
|
327
|
+
client.will_qos.should == 1
|
148
328
|
end
|
149
329
|
|
150
330
|
it "should include the will in the CONNECT message" do
|
151
|
-
|
152
|
-
|
331
|
+
client.connect('myclient')
|
332
|
+
socket.string.should ==
|
153
333
|
"\x10\x24"+
|
154
334
|
"\x00\x06MQIsdp"+
|
155
335
|
"\x03\x0e\x00\x0f"+
|
@@ -161,218 +341,217 @@ describe MQTT::Client do
|
|
161
341
|
end
|
162
342
|
|
163
343
|
describe "calling 'connect' on the class" do
|
164
|
-
before(:each) do
|
165
|
-
TCPSocket.stub(:new).and_return(@socket)
|
166
|
-
MQTT::Client.stub(:new).and_return(@client)
|
167
|
-
Thread.stub(:new)
|
168
|
-
@client.stub(:receive_connack)
|
169
|
-
end
|
170
|
-
|
171
344
|
it "should create a new client object" do
|
172
|
-
MQTT::Client
|
345
|
+
client = double("MQTT::Client")
|
346
|
+
allow(client).to receive(:connect)
|
347
|
+
expect(MQTT::Client).to receive(:new).once.and_return(client)
|
173
348
|
MQTT::Client.connect
|
174
349
|
end
|
175
350
|
|
176
351
|
it "should call connect new client object" do
|
177
|
-
|
352
|
+
client = double("MQTT::Client")
|
353
|
+
expect(client).to receive(:connect)
|
354
|
+
allow(MQTT::Client).to receive(:new).once.and_return(client)
|
178
355
|
MQTT::Client.connect
|
179
356
|
end
|
180
357
|
|
181
358
|
it "should return the new client object" do
|
182
|
-
client = MQTT::Client
|
183
|
-
client.
|
359
|
+
client = double("MQTT::Client")
|
360
|
+
allow(client).to receive(:connect)
|
361
|
+
allow(MQTT::Client).to receive(:new).once.and_return(client)
|
362
|
+
MQTT::Client.connect.should == client
|
184
363
|
end
|
185
364
|
end
|
186
365
|
|
187
366
|
describe "when calling the 'receive_connack' method" do
|
188
367
|
before(:each) do
|
189
|
-
|
190
|
-
IO.stub(:select).and_return([[
|
368
|
+
client.instance_variable_set('@socket', socket)
|
369
|
+
IO.stub(:select).and_return([[socket], [], []])
|
191
370
|
end
|
192
371
|
|
193
372
|
it "should not throw an exception for a successful CONNACK packet" do
|
194
|
-
|
195
|
-
|
196
|
-
lambda {
|
373
|
+
socket.write("\x20\x02\x00\x00")
|
374
|
+
socket.rewind
|
375
|
+
lambda { client.send(:receive_connack) }.should_not raise_error
|
197
376
|
end
|
198
377
|
|
199
378
|
it "should throw an exception if the packet type isn't CONNACK" do
|
200
|
-
|
201
|
-
|
202
|
-
lambda {
|
379
|
+
socket.write("\xD0\x00")
|
380
|
+
socket.rewind
|
381
|
+
lambda { client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException)
|
203
382
|
end
|
204
383
|
|
205
384
|
it "should throw an exception if the CONNACK packet return code is 'unacceptable protocol version'" do
|
206
|
-
|
207
|
-
|
208
|
-
lambda {
|
385
|
+
socket.write("\x20\x02\x00\x01")
|
386
|
+
socket.rewind
|
387
|
+
lambda { client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException, /unacceptable protocol version/i)
|
209
388
|
end
|
210
389
|
|
211
390
|
it "should throw an exception if the CONNACK packet return code is 'client identifier rejected'" do
|
212
|
-
|
213
|
-
|
214
|
-
lambda {
|
391
|
+
socket.write("\x20\x02\x00\x02")
|
392
|
+
socket.rewind
|
393
|
+
lambda { client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException, /client identifier rejected/i)
|
215
394
|
end
|
216
395
|
|
217
396
|
it "should throw an exception if the CONNACK packet return code is 'broker unavailable'" do
|
218
|
-
|
219
|
-
|
220
|
-
lambda {
|
397
|
+
socket.write("\x20\x02\x00\x03")
|
398
|
+
socket.rewind
|
399
|
+
lambda { client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException, /broker unavailable/i)
|
221
400
|
end
|
222
401
|
|
223
402
|
it "should throw an exception if the CONNACK packet return code is an unknown" do
|
224
|
-
|
225
|
-
|
226
|
-
lambda {
|
403
|
+
socket.write("\x20\x02\x00\xAA")
|
404
|
+
socket.rewind
|
405
|
+
lambda { client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException, /connection refused/i)
|
227
406
|
end
|
228
407
|
end
|
229
408
|
|
230
409
|
describe "when calling the 'disconnect' method" do
|
231
410
|
before(:each) do
|
232
411
|
thread = double('Read Thread', :alive? => true, :kill => true)
|
233
|
-
|
234
|
-
|
412
|
+
client.instance_variable_set('@socket', socket)
|
413
|
+
client.instance_variable_set('@read_thread', thread)
|
235
414
|
end
|
236
415
|
|
237
416
|
it "should not do anything if the socket is already disconnected" do
|
238
|
-
|
239
|
-
|
240
|
-
|
417
|
+
client.stub(:connected?).and_return(false)
|
418
|
+
client.disconnect(true)
|
419
|
+
socket.string.should == ""
|
241
420
|
end
|
242
421
|
|
243
422
|
it "should write a valid DISCONNECT packet to the socket if connected and the send_msg=true an" do
|
244
|
-
|
245
|
-
|
246
|
-
|
423
|
+
client.stub(:connected?).and_return(true)
|
424
|
+
client.disconnect(true)
|
425
|
+
socket.string.should == "\xE0\x00"
|
247
426
|
end
|
248
427
|
|
249
428
|
it "should not write anything to the socket if the send_msg=false" do
|
250
|
-
|
251
|
-
|
252
|
-
|
429
|
+
client.stub(:connected?).and_return(true)
|
430
|
+
client.disconnect(false)
|
431
|
+
socket.string.should be_empty
|
253
432
|
end
|
254
433
|
|
255
434
|
it "should call the close method on the socket" do
|
256
|
-
|
257
|
-
|
435
|
+
socket.should_receive(:close)
|
436
|
+
client.disconnect
|
258
437
|
end
|
259
438
|
end
|
260
439
|
|
261
440
|
describe "when calling the 'ping' method" do
|
262
441
|
before(:each) do
|
263
|
-
|
442
|
+
client.instance_variable_set('@socket', socket)
|
264
443
|
end
|
265
444
|
|
266
445
|
it "should write a valid PINGREQ packet to the socket" do
|
267
|
-
|
268
|
-
|
446
|
+
client.ping
|
447
|
+
socket.string.should == "\xC0\x00"
|
269
448
|
end
|
270
449
|
|
271
450
|
it "should update the time a ping was last sent" do
|
272
|
-
|
273
|
-
|
274
|
-
|
451
|
+
client.instance_variable_set('@last_pingreq', 0)
|
452
|
+
client.ping
|
453
|
+
client.instance_variable_get('@last_pingreq').should_not == 0
|
275
454
|
end
|
276
455
|
end
|
277
456
|
|
278
457
|
describe "when calling the 'publish' method" do
|
279
458
|
before(:each) do
|
280
|
-
|
459
|
+
client.instance_variable_set('@socket', socket)
|
281
460
|
end
|
282
461
|
|
283
462
|
it "should write a valid PUBLISH packet to the socket without the retain flag" do
|
284
|
-
|
285
|
-
|
463
|
+
client.publish('topic','payload', false, 0)
|
464
|
+
socket.string.should == "\x30\x0e\x00\x05topicpayload"
|
286
465
|
end
|
287
466
|
|
288
467
|
it "should write a valid PUBLISH packet to the socket with the retain flag set" do
|
289
|
-
|
290
|
-
|
468
|
+
client.publish('topic','payload', true, 0)
|
469
|
+
socket.string.should == "\x31\x0e\x00\x05topicpayload"
|
291
470
|
end
|
292
471
|
|
293
472
|
it "should write a valid PUBLISH packet to the socket with the QOS set to 1" do
|
294
|
-
|
295
|
-
|
473
|
+
client.publish('topic','payload', false, 1)
|
474
|
+
socket.string.should == "\x32\x10\x00\x05topic\x00\x01payload"
|
296
475
|
end
|
297
476
|
|
298
477
|
it "should write a valid PUBLISH packet to the socket with the QOS set to 2" do
|
299
|
-
|
300
|
-
|
478
|
+
client.publish('topic','payload', false, 2)
|
479
|
+
socket.string.should == "\x34\x10\x00\x05topic\x00\x01payload"
|
301
480
|
end
|
302
481
|
end
|
303
482
|
|
304
483
|
describe "when calling the 'subscribe' method" do
|
305
484
|
before(:each) do
|
306
|
-
|
485
|
+
client.instance_variable_set('@socket', socket)
|
307
486
|
end
|
308
487
|
|
309
488
|
it "should write a valid SUBSCRIBE packet to the socket if given a single topic String" do
|
310
|
-
|
311
|
-
|
489
|
+
client.subscribe('a/b')
|
490
|
+
socket.string.should == "\x82\x08\x00\x01\x00\x03a/b\x00"
|
312
491
|
end
|
313
492
|
|
314
493
|
it "should write a valid SUBSCRIBE packet to the socket if given a two topic Strings in an Array" do
|
315
|
-
|
316
|
-
|
494
|
+
client.subscribe('a/b','c/d')
|
495
|
+
socket.string.should == "\x82\x0e\x00\x01\x00\x03a/b\x00\x00\x03c/d\x00"
|
317
496
|
end
|
318
497
|
|
319
498
|
it "should write a valid SUBSCRIBE packet to the socket if given a two topic Strings with QoS in an Array" do
|
320
|
-
|
321
|
-
|
499
|
+
client.subscribe(['a/b',0],['c/d',1])
|
500
|
+
socket.string.should == "\x82\x0e\x00\x01\x00\x03a/b\x00\x00\x03c/d\x01"
|
322
501
|
end
|
323
502
|
|
324
503
|
it "should write a valid SUBSCRIBE packet to the socket if given a two topic Strings with QoS in a Hash" do
|
325
|
-
|
326
|
-
|
504
|
+
client.subscribe('a/b' => 0,'c/d' => 1)
|
505
|
+
socket.string.should == "\x82\x0e\x00\x01\x00\x03a/b\x00\x00\x03c/d\x01"
|
327
506
|
end
|
328
507
|
end
|
329
508
|
|
330
509
|
describe "when calling the 'queue_length' method" do
|
331
510
|
it "should return 0 if there are no incoming messages waiting" do
|
332
|
-
|
511
|
+
client.queue_length.should == 0
|
333
512
|
end
|
334
513
|
|
335
514
|
it "should return 1 if there is one incoming message waiting" do
|
336
515
|
inject_packet(:topic => 'topic0', :payload => 'payload0', :qos => 0)
|
337
|
-
|
516
|
+
client.queue_length.should == 1
|
338
517
|
end
|
339
518
|
|
340
519
|
it "should return 2 if there are two incoming message waiting" do
|
341
520
|
inject_packet(:topic => 'topic0', :payload => 'payload0', :qos => 0)
|
342
521
|
inject_packet(:topic => 'topic0', :payload => 'payload1', :qos => 0)
|
343
|
-
|
522
|
+
client.queue_length.should == 2
|
344
523
|
end
|
345
524
|
end
|
346
525
|
|
347
526
|
describe "when calling the 'queue_emtpy?' method" do
|
348
527
|
it "should return return true if there no incoming messages waiting" do
|
349
|
-
|
528
|
+
client.queue_empty?.should be_true
|
350
529
|
end
|
351
530
|
|
352
531
|
it "should return return false if there is an incoming messages waiting" do
|
353
532
|
inject_packet(:topic => 'topic0', :payload => 'payload0', :qos => 0)
|
354
|
-
|
533
|
+
client.queue_empty?.should be_false
|
355
534
|
end
|
356
535
|
end
|
357
536
|
|
358
537
|
describe "when calling the 'get' method" do
|
359
538
|
before(:each) do
|
360
|
-
|
539
|
+
client.instance_variable_set('@socket', socket)
|
361
540
|
end
|
362
541
|
|
363
542
|
it "should successfull receive a valid PUBLISH packet with a QoS 0" do
|
364
543
|
inject_packet(:topic => 'topic0', :payload => 'payload0', :qos => 0)
|
365
|
-
topic,payload =
|
544
|
+
topic,payload = client.get
|
366
545
|
topic.should == 'topic0'
|
367
546
|
payload.should == 'payload0'
|
368
547
|
end
|
369
548
|
|
370
549
|
it "should successfull receive a valid PUBLISH packet with a QoS 1" do
|
371
550
|
inject_packet(:topic => 'topic1', :payload => 'payload1', :qos => 1)
|
372
|
-
topic,payload =
|
551
|
+
topic,payload = client.get
|
373
552
|
topic.should == 'topic1'
|
374
553
|
payload.should == 'payload1'
|
375
|
-
|
554
|
+
client.queue_empty?.should be_true
|
376
555
|
end
|
377
556
|
|
378
557
|
context "with a block" do
|
@@ -380,7 +559,7 @@ describe MQTT::Client do
|
|
380
559
|
inject_packet(:topic => 'topic0', :payload => 'payload0')
|
381
560
|
inject_packet(:topic => 'topic1', :payload => 'payload1')
|
382
561
|
payloads = []
|
383
|
-
|
562
|
+
client.get do |topic,payload|
|
384
563
|
payloads << payload
|
385
564
|
break if payloads.size > 1
|
386
565
|
end
|
@@ -392,12 +571,12 @@ describe MQTT::Client do
|
|
392
571
|
|
393
572
|
describe "when calling the 'get_packet' method" do
|
394
573
|
before(:each) do
|
395
|
-
|
574
|
+
client.instance_variable_set('@socket', socket)
|
396
575
|
end
|
397
576
|
|
398
577
|
it "should successfull receive a valid PUBLISH packet with a QoS 0" do
|
399
578
|
inject_packet(:topic => 'topic0', :payload => 'payload0', :qos => 0)
|
400
|
-
packet =
|
579
|
+
packet = client.get_packet
|
401
580
|
packet.class.should == MQTT::Packet::Publish
|
402
581
|
packet.qos.should == 0
|
403
582
|
packet.topic.should == 'topic0'
|
@@ -406,12 +585,12 @@ describe MQTT::Client do
|
|
406
585
|
|
407
586
|
it "should successfull receive a valid PUBLISH packet with a QoS 1" do
|
408
587
|
inject_packet(:topic => 'topic1', :payload => 'payload1', :qos => 1)
|
409
|
-
packet =
|
588
|
+
packet = client.get_packet
|
410
589
|
packet.class.should == MQTT::Packet::Publish
|
411
590
|
packet.qos.should == 1
|
412
591
|
packet.topic.should == 'topic1'
|
413
592
|
packet.payload.should == 'payload1'
|
414
|
-
|
593
|
+
client.queue_empty?.should be_true
|
415
594
|
end
|
416
595
|
|
417
596
|
context "with a block" do
|
@@ -419,7 +598,7 @@ describe MQTT::Client do
|
|
419
598
|
inject_packet(:topic => 'topic0', :payload => 'payload0')
|
420
599
|
inject_packet(:topic => 'topic1', :payload => 'payload1')
|
421
600
|
packets = []
|
422
|
-
|
601
|
+
client.get_packet do |packet|
|
423
602
|
packets << packet
|
424
603
|
break if packets.size > 1
|
425
604
|
end
|
@@ -431,97 +610,98 @@ describe MQTT::Client do
|
|
431
610
|
|
432
611
|
describe "when calling the 'unsubscribe' method" do
|
433
612
|
before(:each) do
|
434
|
-
|
613
|
+
client.instance_variable_set('@socket', socket)
|
435
614
|
end
|
436
615
|
|
437
616
|
it "should write a valid UNSUBSCRIBE packet to the socket if given a single topic String" do
|
438
|
-
|
439
|
-
|
617
|
+
client.unsubscribe('a/b')
|
618
|
+
socket.string.should == "\xa2\x07\x00\x01\x00\x03a/b"
|
440
619
|
end
|
441
620
|
|
442
621
|
it "should write a valid UNSUBSCRIBE packet to the socket if given a two topic Strings" do
|
443
|
-
|
444
|
-
|
622
|
+
client.unsubscribe('a/b','c/d')
|
623
|
+
socket.string.should == "\xa2\x0c\x00\x01\x00\x03a/b\x00\x03c/d"
|
624
|
+
end
|
625
|
+
|
626
|
+
it "should write a valid UNSUBSCRIBE packet to the socket if given an array of Strings" do
|
627
|
+
client.unsubscribe(['a/b','c/d'])
|
628
|
+
socket.string.should == "\xa2\x0c\x00\x01\x00\x03a/b\x00\x03c/d"
|
445
629
|
end
|
446
630
|
end
|
447
631
|
|
448
632
|
describe "when calling the 'receive_packet' method" do
|
449
633
|
before(:each) do
|
450
|
-
|
451
|
-
IO.stub(:select).and_return([[
|
452
|
-
@read_queue =
|
634
|
+
client.instance_variable_set('@socket', socket)
|
635
|
+
IO.stub(:select).and_return([[socket], [], []])
|
636
|
+
@read_queue = client.instance_variable_get('@read_queue')
|
453
637
|
@parent_thread = Thread.current[:parent] = double('Parent Thread')
|
454
638
|
@parent_thread.stub(:raise)
|
455
639
|
end
|
456
640
|
|
457
641
|
it "should put PUBLISH messages on to the read queue" do
|
458
|
-
|
459
|
-
|
460
|
-
|
642
|
+
socket.write("\x30\x0e\x00\x05topicpayload")
|
643
|
+
socket.rewind
|
644
|
+
client.send(:receive_packet)
|
461
645
|
@read_queue.size.should == 1
|
462
646
|
end
|
463
647
|
|
464
648
|
it "should not put other messages on to the read queue" do
|
465
|
-
|
466
|
-
|
467
|
-
|
649
|
+
socket.write("\x20\x02\x00\x00")
|
650
|
+
socket.rewind
|
651
|
+
client.send(:receive_packet)
|
468
652
|
@read_queue.size.should == 0
|
469
653
|
end
|
470
654
|
|
471
655
|
it "should send a ping packet if one is due" do
|
472
656
|
IO.should_receive(:select).and_return(nil)
|
473
|
-
|
474
|
-
|
475
|
-
|
657
|
+
client.instance_variable_set('@last_pingreq', Time.at(0))
|
658
|
+
client.should_receive(:ping).once
|
659
|
+
client.send(:receive_packet)
|
476
660
|
end
|
477
661
|
|
478
662
|
it "should close the socket if there is an exception" do
|
479
|
-
|
663
|
+
socket.should_receive(:close).once
|
480
664
|
MQTT::Packet.stub(:read).and_raise(MQTT::Exception)
|
481
|
-
|
665
|
+
client.send(:receive_packet)
|
482
666
|
end
|
483
667
|
|
484
668
|
it "should pass exceptions up to parent thread" do
|
485
669
|
@parent_thread.should_receive(:raise).once
|
486
670
|
MQTT::Packet.stub(:read).and_raise(MQTT::Exception)
|
487
|
-
|
671
|
+
client.send(:receive_packet)
|
488
672
|
end
|
489
673
|
end
|
490
674
|
|
491
675
|
describe "generating a client identifier" do
|
492
676
|
context "with default parameters" do
|
493
|
-
|
494
|
-
@client_id = MQTT::Client.generate_client_id
|
495
|
-
end
|
677
|
+
let(:client_id) { MQTT::Client.generate_client_id }
|
496
678
|
|
497
679
|
it "should be less or equal to 23 characters long" do
|
498
|
-
|
680
|
+
client_id.length.should <= 23
|
499
681
|
end
|
500
682
|
|
501
683
|
it "should have a prefix of ruby_" do
|
502
|
-
|
684
|
+
client_id.should match(/^ruby_/)
|
503
685
|
end
|
504
686
|
|
505
687
|
it "should end in 16 characters of lowercase letters and numbers" do
|
506
|
-
|
688
|
+
client_id.should match(/_[a-z0-9]{16}$/)
|
507
689
|
end
|
508
690
|
end
|
509
691
|
|
510
692
|
context "with an alternative prefix" do
|
511
|
-
|
512
|
-
@client_id = MQTT::Client.generate_client_id('test_')
|
513
|
-
end
|
693
|
+
let(:client_id) { MQTT::Client.generate_client_id('test_') }
|
514
694
|
|
515
695
|
it "should be less or equal to 23 characters long" do
|
516
|
-
|
696
|
+
client_id.length.should <= 23
|
517
697
|
end
|
518
698
|
|
519
699
|
it "should have a prefix of test_" do
|
520
|
-
|
700
|
+
client_id.should match(/^test_/)
|
521
701
|
end
|
522
702
|
|
523
703
|
it "should end in 16 characters of lowercase letters and numbers" do
|
524
|
-
|
704
|
+
client_id.should match(/_[a-z0-9]{16}$/)
|
525
705
|
end
|
526
706
|
end
|
527
707
|
end
|
@@ -530,7 +710,7 @@ describe MQTT::Client do
|
|
530
710
|
|
531
711
|
def inject_packet(opts={})
|
532
712
|
packet = MQTT::Packet::Publish.new(opts)
|
533
|
-
|
713
|
+
client.instance_variable_get('@read_queue').push(packet)
|
534
714
|
end
|
535
715
|
|
536
716
|
end
|