mqtt 0.2.0 → 0.3.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/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
|