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