paho-mqtt 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 16c0a069f566b0c91b896cbcc0a4b3ca2eb694f6
4
- data.tar.gz: bd03230d51b7bc37be071caac903ce8537073b6b
3
+ metadata.gz: 81783208a50d429eede136e757bddf982ed2fd83
4
+ data.tar.gz: 4d1c34982623e35713e8af79da00cc6bf14d91eb
5
5
  SHA512:
6
- metadata.gz: 0c3a8a75de74ef2df07b3e6df1abcf694e07dd425e589169a8c7696e8131b55aba92b41daf6525e60ef3210347f33139ea399eb8092877dedc40c6f986b022e5
7
- data.tar.gz: 53e67740ea1e52c1a0e73517079d1b7b0ce1f15802ea22f9a880a00ba2e514b7e715e7a06b293931e1a561cbd2beb2dd8439dee906a6fdfbc183c13f0ce7f87c
6
+ metadata.gz: 02f36654df8e3f0ae3a3e47f64708ca0f770076766c7e593755d8d55dc14950e40b1eab91631f4aab7815e83653f83e2ca05dd02f8565820a3d2b089a2652cee
7
+ data.tar.gz: 50456a5604f7318622cd1809b8859db2845c7c5a0b7b4bae15d09333d957248c40cced139c7c54ac6efff9efda6985aad4c84b50de33880491ad4a369a247ff4
data/lib/paho-mqtt.rb CHANGED
@@ -1,8 +1,27 @@
1
+ # Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
2
+ #
3
+ # All rights reserved. This program and the accompanying materials
4
+ # are made available under the terms of the Eclipse Public License v1.0
5
+ # and Eclipse Distribution License v1.0 which accompany this distribution.
6
+ #
7
+ # The Eclipse Public License is available at
8
+ # https://eclipse.org/org/documents/epl-v10.php.
9
+ # and the Eclipse Distribution License is available at
10
+ # https://eclipse.org/org/documents/edl-v10.php.
11
+ #
12
+ # Contributors:
13
+ # Pierre Goudet - initial committer
14
+
1
15
  require "paho_mqtt/version"
2
16
  require "paho_mqtt/client"
3
17
  require "paho_mqtt/packet"
18
+ require 'logger'
4
19
 
5
20
  module PahoMqtt
21
+ extend self
22
+
23
+ attr_accessor :logger
24
+
6
25
  # Default connection setup
7
26
  DEFAULT_SSL_PORT = 8883
8
27
  DEFAULT_PORT = 1883
@@ -47,15 +66,67 @@ module PahoMqtt
47
66
  nil
48
67
  ]
49
68
 
69
+ CONNACK_ERROR_MESSAGE = {
70
+ 0x02 => "Client Identifier is correct but not allowed by remote server.",
71
+ 0x03 => "Connection established but MQTT service unvailable on remote server.",
72
+ 0x04 => "User name or user password is malformed.",
73
+ 0x05 => "Client is not authorized to connect to the server."
74
+ }
75
+
76
+ CLIENT_ATTR_DEFAULTS = {
77
+ :host => "",
78
+ :port => nil,
79
+ :mqtt_version => '3.1.1',
80
+ :clean_session => true,
81
+ :persistent => false,
82
+ :blocking => false,
83
+ :client_id => nil,
84
+ :username => nil,
85
+ :password => nil,
86
+ :ssl => false,
87
+ :will_topic => nil,
88
+ :will_payload => nil,
89
+ :will_qos => 0,
90
+ :will_retain => false,
91
+ :keep_alive => 60,
92
+ :ack_timeout => 5,
93
+ :on_connack => nil,
94
+ :on_suback => nil,
95
+ :on_unsuback => nil,
96
+ :on_puback => nil,
97
+ :on_pubrel => nil,
98
+ :on_pubrec => nil,
99
+ :on_pubcomp => nil,
100
+ :on_message => nil,
101
+ }
102
+
50
103
  Thread.abort_on_exception = true
51
104
 
105
+ def logger=(logger_path)
106
+ file = File.open(logger_path, "a+")
107
+ log_file = Logger.new(file)
108
+ log_file.level = Logger::DEBUG
109
+ @action_manager.logger = log_file
110
+ end
111
+
112
+ def logger
113
+ @logger
114
+ end
115
+
116
+ def logger?
117
+ @logger.is_a?(Logger)
118
+ end
119
+
52
120
  class Exception < ::Exception
53
121
  end
54
122
 
55
123
  class ProtocolViolation < PahoMqtt::Exception
56
124
  end
57
125
 
58
- class ParameterException < PahoMqtt::Exception
126
+ class WritingException < PahoMqtt::Exception
127
+ end
128
+
129
+ class ReadingException < PahoMqtt::Exception
59
130
  end
60
131
 
61
132
  class PacketException < PahoMqtt::Exception
@@ -1,12 +1,26 @@
1
- require 'openssl'
2
- require 'socket'
3
- require 'logger'
1
+ # Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
2
+ #
3
+ # All rights reserved. This program and the accompanying materials
4
+ # are made available under the terms of the Eclipse Public License v1.0
5
+ # and Eclipse Distribution License v1.0 which accompany this distribution.
6
+ #
7
+ # The Eclipse Public License is available at
8
+ # https://eclipse.org/org/documents/epl-v10.php.
9
+ # and the Eclipse Distribution License is available at
10
+ # https://eclipse.org/org/documents/edl-v10.php.
11
+ #
12
+ # Contributors:
13
+ # Pierre Goudet - initial committer
14
+
15
+ require 'paho_mqtt/handler'
16
+ require 'paho_mqtt/connection_helper'
17
+ require 'paho_mqtt/sender'
18
+ require 'paho_mqtt/publisher'
19
+ require 'paho_mqtt/subscriber'
20
+ require 'paho_mqtt/ssl_helper'
4
21
 
5
22
  module PahoMqtt
6
- class Client
7
- # Log file
8
- attr_accessor :logger
9
-
23
+ class Client
10
24
  # Connection related attributes:
11
25
  attr_accessor :host
12
26
  attr_accessor :port
@@ -18,7 +32,7 @@ module PahoMqtt
18
32
  attr_accessor :username
19
33
  attr_accessor :password
20
34
  attr_accessor :ssl
21
-
35
+
22
36
  # Last will attributes:
23
37
  attr_accessor :will_topic
24
38
  attr_accessor :will_payload
@@ -29,100 +43,45 @@ module PahoMqtt
29
43
  attr_accessor :keep_alive
30
44
  attr_accessor :ack_timeout
31
45
 
32
- #Callback attributes
33
- attr_accessor :on_message
34
- attr_accessor :on_connack
35
- attr_accessor :on_suback
36
- attr_accessor :on_unsuback
37
- attr_accessor :on_puback
38
- attr_accessor :on_pubrel
39
- attr_accessor :on_pubrec
40
- attr_accessor :on_pubcomp
41
-
42
46
  #Read Only attribute
43
- attr_reader :registered_callback
44
- attr_reader :subscribed_topics
45
47
  attr_reader :connection_state
48
+ attr_reader :ssl_context
46
49
 
47
- ATTR_DEFAULTS = {
48
- :logger => nil,
49
- :host => "",
50
- :port => nil,
51
- :mqtt_version => '3.1.1',
52
- :clean_session => true,
53
- :persistent => false,
54
- :blocking => false,
55
- :client_id => nil,
56
- :username => nil,
57
- :password => nil,
58
- :ssl => false,
59
- :will_topic => nil,
60
- :will_payload => nil,
61
- :will_qos => 0,
62
- :will_retain => false,
63
- :keep_alive => 60,
64
- :ack_timeout => 5,
65
- :on_connack => nil,
66
- :on_suback => nil,
67
- :on_unsuback => nil,
68
- :on_puback => nil,
69
- :on_pubrel => nil,
70
- :on_pubrec => nil,
71
- :on_pubcomp => nil,
72
- :on_message => nil,
73
- }
74
-
75
50
  def initialize(*args)
51
+ @last_ping_resp = Time.now
52
+ @last_packet_id = 0
53
+ @ssl_context = nil
54
+ @sender = nil
55
+ @handler = Handler.new
56
+ @connection_helper = nil
57
+ @connection_state = MQTT_CS_DISCONNECT
58
+ @connection_state_mutex = Mutex.new
59
+ @mqtt_thread = nil
60
+ @reconnect_thread = nil
61
+ @id_mutex = Mutex.new
62
+
76
63
  if args.last.is_a?(Hash)
77
64
  attr = args.pop
78
65
  else
79
66
  attr = {}
80
67
  end
81
68
 
82
- ATTR_DEFAULTS.merge(attr).each_pair do |k,v|
69
+ CLIENT_ATTR_DEFAULTS.merge(attr).each_pair do |k,v|
83
70
  self.send("#{k}=", v)
84
71
  end
85
72
 
86
73
  if @port.nil?
87
74
  @port
88
75
  if @ssl
89
- @port = PahoMqtt::DEFAULT_SSL_PORT
76
+ @port = DEFAULT_SSL_PORT
90
77
  else
91
- @port = PahoMqtt::DEFAULT_PORT
78
+ @port = DEFAULT_PORT
92
79
  end
93
80
  end
94
81
 
95
82
  if @client_id.nil? || @client_id == ""
96
83
  @client_id = generate_client_id
97
84
  end
98
-
99
- @last_ping_req = Time.now
100
- @last_ping_resp = Time.now
101
- @last_packet_id = 0
102
- @socket = nil
103
- @ssl_context = nil
104
- @writing_mutex = Mutex.new
105
- @writing_queue = []
106
- @connection_state = MQTT_CS_DISCONNECT
107
- @connection_state_mutex = Mutex.new
108
- @subscribed_mutex = Mutex.new
109
- @subscribed_topics = []
110
- @registered_callback = []
111
- @waiting_suback = []
112
- @suback_mutex = Mutex.new
113
- @waiting_unsuback = []
114
- @unsuback_mutex = Mutex.new
115
- @mqtt_thread = nil
116
- @reconnect_thread = nil
117
-
118
- @puback_mutex = Mutex.new
119
- @pubrec_mutex = Mutex.new
120
- @pubrel_mutex = Mutex.new
121
- @pubcomp_mutex = Mutex.new
122
- @waiting_puback = []
123
- @waiting_pubrec = []
124
- @waiting_pubrel = []
125
- @waiting_pubcomp = []
126
85
  end
127
86
 
128
87
  def generate_client_id(prefix='paho_ruby', lenght=16)
@@ -130,25 +89,11 @@ module PahoMqtt
130
89
  @client_id = prefix << Array.new(lenght) { charset.sample }.join
131
90
  end
132
91
 
133
- def ssl_context
134
- @ssl_context ||= OpenSSL::SSL::SSLContext.new
135
- end
136
-
137
92
  def config_ssl_context(cert_path, key_path, ca_path=nil)
138
93
  @ssl ||= true
139
- @ssl_context = ssl_context
140
- self.cert = cert_path
141
- self.key = key_path
142
- self.root_ca = ca_path
94
+ @ssl_context = SSLHelper.config_ssl_context(cert_path, key_path, ca_path)
143
95
  end
144
96
 
145
- def config_will(topic, payload="", retain=false, qos=0)
146
- @will_topic = topic
147
- @will_payload = payload
148
- @will_retain = retain
149
- @will_qos = qos
150
- end
151
-
152
97
  def connect(host=@host, port=@port, keep_alive=@keep_alive, persistent=@persistent, blocking=@blocking)
153
98
  @persistent = persistent
154
99
  @blocking = blocking
@@ -158,11 +103,19 @@ module PahoMqtt
158
103
  @connection_state_mutex.synchronize {
159
104
  @connection_state = MQTT_CS_NEW
160
105
  }
161
- setup_connection
162
- connect_async(host, port, keep_alive) unless @blocking
106
+ @mqtt_thread.kill unless @mqtt_thread.nil?
107
+ init_connection
108
+ @connection_helper.send_connect(session_params)
109
+ begin
110
+ @connection_state = @connection_helper.do_connect(reconnect?)
111
+ rescue LowVersionException
112
+ downgrade_version
113
+ end
114
+ build_pubsub
115
+ daemon_mode unless @blocking || !connected?
163
116
  end
164
117
 
165
- def connect_async(host, port=1883, keep_alive)
118
+ def daemon_mode
166
119
  @mqtt_thread = Thread.new do
167
120
  @reconnect_thread.kill unless @reconnect_thread.nil? || !@reconnect_thread.alive?
168
121
  while connected? do
@@ -174,20 +127,34 @@ module PahoMqtt
174
127
  def connected?
175
128
  @connection_state == MQTT_CS_CONNECTED
176
129
  end
177
-
130
+
131
+ def reconnect?
132
+ Thread.current == @reconnect_thread
133
+ end
134
+
178
135
  def loop_write(max_packet=MAX_WRITING)
179
- @writing_mutex.synchronize {
180
- cnt = 0
181
- while !@writing_queue.empty? && cnt < max_packet do
182
- send_packet(@writing_queue.shift)
183
- cnt += 1
136
+ begin
137
+ @sender.writing_loop(max_packet)
138
+ rescue WritingException
139
+ if check_persistence
140
+ reconnect
141
+ else
142
+ raise WritingException
184
143
  end
185
- }
144
+ end
186
145
  end
187
146
 
188
147
  def loop_read(max_packet=MAX_READ)
189
148
  max_packet.times do
190
- receive_packet
149
+ begin
150
+ @handler.receive_packet
151
+ rescue ReadingException
152
+ if check_persistence
153
+ reconnect
154
+ else
155
+ raise ReadingException
156
+ end
157
+ end
191
158
  end
192
159
  end
193
160
 
@@ -199,19 +166,17 @@ module PahoMqtt
199
166
  end
200
167
 
201
168
  def loop_misc
202
- check_keep_alive
203
- check_ack_alive(@waiting_puback, @puback_mutex, MAX_PUBACK)
204
- check_ack_alive(@waiting_pubrec, @pubrec_mutex, MAX_PUBREC)
205
- check_ack_alive(@waiting_pubrel, @pubrel_mutex, MAX_PUBREL)
206
- check_ack_alive(@waiting_pubcomp, @pubcomp_mutex, MAX_PUBCOMP)
207
- check_ack_alive(@waiting_suback, @suback_mutex, @waiting_suback.length)
208
- check_ack_alive(@waiting_unsuback, @unsuback_mutex, @waiting_unsuback.length)
169
+ if @connection_helper.check_keep_alive(@persistent, @handler.last_ping_resp, @keep_alive) == MQTT_CS_DISCONNECT
170
+ reconnect if check_persistence
171
+ end
172
+ @publisher.check_waiting_publisher
173
+ @subscriber.check_waiting_subscriber
209
174
  end
210
175
 
211
176
  def reconnect(retry_time=RECONNECT_RETRY_TIME, retry_tempo=RECONNECT_RETRY_TEMPO)
212
177
  @reconnect_thread = Thread.new do
213
178
  retry_time.times do
214
- @logger.debug("New reconnect atempt...") if @logger.is_a?(Logger)
179
+ PahoMqtt.logger.debug("New reconnect atempt...") if PahoMqtt.logger?
215
180
  connect
216
181
  if connected?
217
182
  break
@@ -220,7 +185,7 @@ module PahoMqtt
220
185
  end
221
186
  end
222
187
  unless connected?
223
- @logger.error("Reconnection atempt counter is over.(#{RECONNECT_RETRY_TIME} times)") if @logger.is_a?(Logger)
188
+ PahoMqtt.logger.error("Reconnection atempt counter is over.(#{RECONNECT_RETRY_TIME} times)") if PahoMqtt.logger?
224
189
  disconnect(false)
225
190
  exit
226
191
  end
@@ -228,38 +193,8 @@ module PahoMqtt
228
193
  end
229
194
 
230
195
  def disconnect(explicit=true)
231
- @logger.debug("Disconnecting from #{@host}") if @logger.is_a?(Logger)
232
-
233
- if explicit
234
- send_disconnect
235
- @mqtt_thread.kill if @mqtt_thread && @mqtt_thread.alive?
236
- @mqtt_thread.kill if @mqtt_thread.alive?
237
- @last_packet_id = 0
238
-
239
- @writing_mutex.synchronize {
240
- @writing_queue = []
241
- }
242
-
243
- @puback_mutex.synchronize {
244
- @waiting_puback = []
245
- }
246
-
247
- @pubrec_mutex.synchronize {
248
- @waiting_pubrec = []
249
- }
250
-
251
- @pubrel_mutex.synchronize {
252
- @waiting_pubrel = []
253
- }
254
-
255
- @pubcomp_mutex.synchronize {
256
- @waiting_pubcomp = []
257
- }
258
- end
259
-
260
- @socket.close unless @socket.nil? || @socket.closed?
261
- @socket = nil
262
-
196
+ @last_packet_id = 0 if explicit
197
+ @connection_helper.do_disconnect(@publisher, explicit, @mqtt_thread)
263
198
  @connection_state_mutex.synchronize {
264
199
  @connection_state = MQTT_CS_DISCONNECT
265
200
  }
@@ -268,656 +203,195 @@ module PahoMqtt
268
203
 
269
204
  def publish(topic, payload="", retain=false, qos=0)
270
205
  if topic == "" || !topic.is_a?(String)
271
- @logger.error("Publish topics is invalid, not a string or empty.") if @logger.is_a?(Logger)
272
- raise ParameterException
206
+ PahoMqtt.logger.error("Publish topics is invalid, not a string or empty.") if PahoMqtt.logger?
207
+ raise ArgumentError
273
208
  end
274
- send_publish(topic, payload, retain, qos)
209
+ id = next_packet_id
210
+ @publisher.send_publish(topic, payload, retain, qos, id)
211
+ MQTT_ERR_SUCCESS
275
212
  end
276
213
 
277
214
  def subscribe(*topics)
278
- unless valid_topics?(topics) == MQTT_ERR_FAIL
279
- send_subscribe(topics)
280
- else
281
- @logger.error("Subscribe topics need one topic or a list of topics.") if @logger.is_a?(Logger)
215
+ begin
216
+ id = next_packet_id
217
+ unless @subscriber.send_subscribe(topics, id) == PahoMqtt::MQTT_ERR_SUCCESS
218
+ reconnect if check_persistence
219
+ end
220
+ MQTT_ERR_SUCCESS
221
+ rescue ProtocolViolation
222
+ PahoMqtt.logger.error("Subscribe topics need one topic or a list of topics.") if PahoMqtt.logger?
282
223
  disconnect(false)
283
224
  raise ProtocolViolation
284
225
  end
285
226
  end
286
227
 
287
228
  def unsubscribe(*topics)
288
- unless valid_topics?(topics) == MQTT_ERR_FAIL
289
- send_unsubscribe(topics)
290
- else
291
- @logger.error("Unsubscribe need at least one topics.") if @logger.is_a?(Logger)
229
+ begin
230
+ id = next_packet_id
231
+ unless @subscriber.send_unsubscribe(topics, id) == MQTT_ERR_SUCCESS
232
+ reconnect if check_persistence
233
+ end
234
+ MQTT_ERR_SUCCESS
235
+ rescue ProtocolViolation
236
+ PahoMqtt.logger.error("Unsubscribe need at least one topics.") if PahoMqtt.logger?
292
237
  disconnect(false)
293
238
  raise ProtocolViolation
294
239
  end
295
240
  end
296
241
 
297
242
  def ping_host
298
- send_pingreq
243
+ @sender.send_pingreq
299
244
  end
300
245
 
301
246
  def add_topic_callback(topic, callback=nil, &block)
302
- if topic.nil?
303
- @logger.error("The topics where the callback is trying to be registered have been found nil.") if @logger.is_a?(Logger)
304
- raise ParameterException
305
- end
306
- remove_topic_callback(topic)
307
-
308
- if block_given?
309
- @registered_callback.push([topic, block])
310
- elsif !(callback.nil?) && callback.class == Proc
311
- @registered_callback.push([topic, callback])
312
- end
313
- MQTT_ERR_SUCCESS
247
+ @handler.register_topic_callback(topic, callback, &block)
314
248
  end
315
249
 
316
250
  def remove_topic_callback(topic)
317
- if topic.nil?
318
- @logger.error("The topics where the callback is trying to be unregistered have been found nil.") if @logger.is_a?(Logger)
319
- raise ParameterException
320
- end
321
- @registered_callback.delete_if {|pair| pair.first == topic}
322
- MQTT_ERR_SUCCESS
251
+ @handler.clear_topic_callback(topic)
323
252
  end
324
253
 
325
254
  def on_connack(&block)
326
- @on_connack = block if block_given?
327
- @on_connack
255
+ @handler.on_connack = block if block_given?
256
+ @handler.on_connack
328
257
  end
329
258
 
330
259
  def on_suback(&block)
331
- @on_suback = block if block_given?
332
- @on_suback
260
+ @handler.on_suback = block if block_given?
261
+ @handler.on_suback
333
262
  end
334
263
 
335
264
  def on_unsuback(&block)
336
- @on_unsuback = block if block_given?
337
- @on_unsuback
265
+ @handler.on_unsuback = block if block_given?
266
+ @handler.on_unsuback
338
267
  end
339
268
 
340
269
  def on_puback(&block)
341
- @on_puback = block if block_given?
342
- @on_puback
270
+ @handler.on_puback = block if block_given?
271
+ @handler.on_puback
343
272
  end
344
273
 
345
274
  def on_pubrec(&block)
346
- @on_pubrec = block if block_given?
347
- @on_pubrec
275
+ @handler.on_pubrec = block if block_given?
276
+ @handler.on_pubrec
348
277
  end
349
278
 
350
279
  def on_pubrel(&block)
351
- @on_pubrel = block if block_given?
352
- @on_pubrel
280
+ @handler.on_pubrel = block if block_given?
281
+ @handler.on_pubrel
353
282
  end
354
283
 
355
284
  def on_pubcomp(&block)
356
- @on_pubcomp = block if block_given?
357
- @on_pubcomp
285
+ @handler.on_pubcomp = block if block_given?
286
+ @handler.on_pubcomp
358
287
  end
359
288
 
360
289
  def on_message(&block)
361
- @on_message = block if block_given?
362
- @on_message
363
- end
364
-
365
- private
366
-
367
- def next_packet_id
368
- @last_packet_id = ( @last_packet_id || 0 ).next
369
- end
370
-
371
- def config_subscription
372
- unless @subscribed_topics == []
373
- new_id = next_packet_id
374
- packet = PahoMqtt::Packet::Subscribe.new(
375
- :id => new_id,
376
- :topics => @subscribed_topics
377
- )
378
- @subscribed_mutex.synchronize {
379
- @subscribed_topics = []
380
- }
381
- @suback_mutex.synchronize {
382
- @waiting_suback.push({ :id => new_id, :packet => packet, :timestamp => Time.now })
383
- }
384
- send_packet(packet)
385
- end
386
- MQTT_ERR_SUCCESS
387
- end
388
-
389
- def config_all_message_queue
390
- config_message_queue(@waiting_puback, @puback_mutex, MAX_PUBACK)
391
- config_message_queue(@waiting_pubrec, @pubrec_mutex, MAX_PUBREC)
392
- config_message_queue(@waiting_pubrel, @pubrel_mutex, MAX_PUBREL)
393
- config_message_queue(@waiting_pubcomp, @pubcomp_mutex, MAX_PUBCOMP)
394
- end
395
-
396
- def config_message_queue(queue, mutex, max_packet)
397
- mutex.synchronize {
398
- cnt = 0
399
- queue.each do |pck|
400
- pck[:packet].dup ||= true
401
- if cnt <= max_packet
402
- append_to_writing(pck[:packet])
403
- cnt += 1
404
- end
405
- end
406
- }
407
- end
408
-
409
- def config_socket
410
- unless @socket.nil?
411
- @socket.close unless @socket.closed?
412
- @socket = nil
413
- end
414
-
415
- unless @host.nil? || @port < 0
416
- begin
417
- tcp_socket = TCPSocket.new(@host, @port)
418
- rescue ::Exception => exp
419
- @logger.warn("Could not open a socket with #{@host} on port #{@port}") if @logger.is_a?(Logger)
420
- end
421
- end
422
-
423
- if @ssl
424
- unless @ssl_context.nil?
425
- @socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl_context)
426
- @socket.sync_close = true
427
- @socket.connect
428
- else
429
- @logger.error("The ssl context was found as nil while the socket's opening.") if @logger.is_a?(Logger)
430
- raise Exception
431
- end
432
- else
433
- @socket = tcp_socket
434
- end
290
+ @handler.on_message = block if block_given?
291
+ @handler.on_message
435
292
  end
436
293
 
437
- def cert=(cert_path)
438
- ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
294
+ def on_connack=(callback)
295
+ @handler.on_connack = callback if callback.is_a?(Proc)
439
296
  end
440
297
 
441
- def key=(key_path)
442
- ssl_context.key = OpenSSL::PKey::RSA.new(File.read(key_path))
298
+ def on_suback=(callback)
299
+ @handler.on_suback = callback if callback.is_a?(Proc)
443
300
  end
444
301
 
445
- def root_ca=(ca_path)
446
- ssl_context.ca_file = ca_path
447
- unless @ca_path.nil?
448
- ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
449
- end
302
+ def on_unsuback=(callback)
303
+ @handler.on_unsuback = callback if callback.is_a?(Proc)
450
304
  end
451
305
 
452
- def setup_connection
453
- @mqtt_thread.kill unless @mqtt_thread.nil?
454
- if @host.nil? || @host == ""
455
- @logger.error("The host was found as nil while the connection setup.") if @logger.is_a?(Logger)
456
- raise ParameterException
457
- end
458
-
459
- if @port.to_i <= 0
460
- @logger.error("The port value is invalid (<= 0). Could not setup the connection.") if @logger.is_a?(Logger)
461
- raise ParameterException
462
- end
463
-
464
- @socket.close unless @socket.nil? || @socket.closed?
465
- @socket = nil
466
-
467
- @last_ping_req = Time.now
468
- @last_ping_resp = Time.now
469
-
470
- @logger.debug("Atempt to connect to host: #{@host}") if @logger.is_a?(Logger)
471
- config_socket
472
-
473
- unless @socket.nil?
474
- send_connect
475
-
476
- # Waiting a Connack packet for "ack_timeout" second from the remote
477
- connect_timeout = Time.now + @ack_timeout
478
- while (Time.now <= connect_timeout) && (!connected?) do
479
- receive_packet
480
- sleep 0.0001
481
- end
482
- end
483
-
484
- unless connected?
485
- @logger.warn("Connection failed. Couldn't recieve a Connack packet from: #{@host}, socket is \"#{@socket}\".") if @logger.is_a?(Logger)
486
- unless Thread.current == @reconnect_thread
487
- raise Exception.new("Connection failed. Check log for more details.")
488
- end
489
- else
490
- config_subscription if Thread.current == @reconnect_thread
491
- end
306
+ def on_puback=(callback)
307
+ @handler.on_puback = callback if callback.is_a?(Proc)
492
308
  end
493
309
 
494
- def check_keep_alive
495
- if @keep_alive >= 0 && connected?
496
- now = Time.now
497
- timeout_req = (@last_ping_req + (@keep_alive * 0.7).ceil)
498
-
499
- if timeout_req <= now && @persistent
500
- send_pingreq
501
- @last_ping_req = now
502
- end
503
-
504
- timeout_resp = @last_ping_resp + (@keep_alive * 1.1).ceil
505
- if timeout_resp <= now
506
- @logger.debug("No activity period over timeout, disconnecting from #{@host}") if @logger.is_a?(Logger)
507
- disconnect(false)
508
- reconnect if @persistent
509
- end
510
- end
310
+ def on_pubrec=(callback)
311
+ @handler.on_pubrec = callback if callback.is_a?(Proc)
511
312
  end
512
313
 
513
- def check_ack_alive(queue, mutex, max_packet)
514
- mutex.synchronize {
515
- now = Time.now
516
- cnt = 0
517
- queue.each do |pck|
518
- if now >= pck[:timestamp] + @ack_timeout
519
- pck[:packet].dup ||= true unless pck[:packet].class == PahoMqtt::Packet::Subscribe || pck[:packet].class == PahoMqtt::Packet::Unsubscribe
520
- unless cnt > max_packet
521
- append_to_writing(pck[:packet])
522
- pck[:timestamp] = now
523
- cnt += 1
524
- end
525
- end
526
- end
527
- }
314
+ def on_pubrel=(callback)
315
+ @handler.on_pubrel = callback if callback.is_a?(Proc)
528
316
  end
529
317
 
530
- def receive_packet
531
- begin
532
- result = IO.select([@socket], [], [], SELECT_TIMEOUT) unless @socket.nil? || @socket.closed?
533
- unless result.nil?
534
- packet = PahoMqtt::Packet::Base.read(@socket)
535
- unless packet.nil?
536
- handle_packet packet
537
- @last_ping_resp = Time.now
538
- end
539
- end
540
- rescue ::Exception => exp
541
- disconnect(false)
542
- if @persistent
543
- reconnect
544
- else
545
- @logger.error("The packet reading have failed.") if @logger.is_a?(Logger)
546
- raise(exp)
547
- end
548
- end
318
+ def on_pubcomp=(callback)
319
+ @handler.on_pubcomp = callback if callback.is_a?(Proc)
549
320
  end
550
321
 
551
- def handle_packet(packet)
552
- @logger.info("New packet #{packet.class} recieved.") if @logger.is_a?(Logger)
553
- if packet.class == PahoMqtt::Packet::Connack
554
- handle_connack(packet)
555
- elsif packet.class == PahoMqtt::Packet::Suback
556
- handle_suback(packet)
557
- elsif packet.class == PahoMqtt::Packet::Unsuback
558
- handle_unsuback(packet)
559
- elsif packet.class == PahoMqtt::Packet::Publish
560
- handle_publish(packet)
561
- elsif packet.class == PahoMqtt::Packet::Puback
562
- handle_puback(packet)
563
- elsif packet.class == PahoMqtt::Packet::Pubrec
564
- handle_pubrec(packet)
565
- elsif packet.class == PahoMqtt::Packet::Pubrel
566
- handle_pubrel(packet)
567
- elsif packet.class == PahoMqtt::Packet::Pubcomp
568
- handle_pubcomp(packet)
569
- elsif packet.class == PahoMqtt::Packet::Pingresp
570
- handle_pingresp
571
- else
572
- @logger.error("The packets header is invalid for packet: #{packet}") if @logger.is_a?(Logger)
573
- raise PacketException
574
- end
322
+ def on_message=(callback)
323
+ @handler.on_message = callback if callback.is_a?(Proc)
575
324
  end
576
325
 
577
- def handle_connack(packet)
578
- if packet.return_code == 0x00
579
- @logger.debug("Connack receive and connection accepted.") if @logger.is_a?(Logger)
580
- if @clean_session && !packet.session_present
581
- @logger.debug("New session created for the client") if @logger.is_a?(Logger)
582
- elsif !@clean_session && !packet.session_present
583
- @logger.debug("No previous session found by server, starting a new one.") if @logger.is_a?(Logger)
584
- elsif !@clean_session && packet.session_present
585
- @logger.debug("Previous session restored by the server.") if @logger.is_a?(Logger)
586
- end
587
- @connection_state_mutex.synchronize{
588
- @connection_state = MQTT_CS_CONNECTED
589
- }
590
- else
591
- disconnect(false)
592
- handle_connack_error(packet.return_code)
593
- end
594
- config_all_message_queue
595
- @writing_mutex.synchronize {
596
- @writing_queue.each do |m|
597
- send_packet(m)
598
- end
599
- }
600
- @on_connack.call(packet) unless @on_connack.nil?
326
+ def registered_callback
327
+ @handler.registered_callback
601
328
  end
602
329
 
603
- def handle_pingresp
604
- @last_ping_resp = Time.now
330
+ def subscribed_topics
331
+ @subscriber.subscribed_topics
605
332
  end
606
333
 
607
- def handle_suback(packet)
608
- adjust_qos = []
609
- max_qos = packet.return_codes
610
- @suback_mutex.synchronize {
611
- adjust_qos, @waiting_suback = @waiting_suback.partition { |pck| pck[:id] == packet.id }
612
- }
613
- if adjust_qos.length == 1
614
- adjust_qos = adjust_qos.first[:packet].topics
615
- adjust_qos.each do |t|
616
- if [0, 1, 2].include?(max_qos[0])
617
- t[1] = max_qos.shift
618
- elsif max_qos[0] == 128
619
- adjust_qos.delete(t)
620
- else
621
- @logger.error("The qos value is invalid in subscribe.") if @logger.is_a?(Logger)
622
- raise PacketException
623
- end
624
- end
625
- else
626
- @logger.error("The packet id is invalid, already used.") if @logger.is_a?(Logger)
627
- raise PacketException
628
- end
629
- @subscribed_mutex.synchronize {
630
- @subscribed_topics.concat(adjust_qos)
631
- }
632
- @on_suback.call(adjust_qos) unless @on_suback.nil?
633
- end
634
334
 
635
- def handle_unsuback(packet)
636
- to_unsub = nil
637
- @unsuback_mutex.synchronize {
638
- to_unsub, @waiting_unsuback = @waiting_unsuback.partition { |pck| pck[:id] == packet.id }
639
- }
640
-
641
- if to_unsub.length == 1
642
- to_unsub = to_unsub.first[:packet].topics
643
- else
644
- @logger.error("The packet id is invalid, already used.") if @logger.is_a?(Logger)
645
- raise PacketException
646
- end
335
+ private
647
336
 
648
- @subscribed_mutex.synchronize {
649
- to_unsub.each do |filter|
650
- @subscribed_topics.delete_if { |topic| match_filter(topic.first, filter.first) }
651
- end
337
+ def next_packet_id
338
+ @id_mutex.synchronize {
339
+ @last_packet_id = ( @last_packet_id || 0 ).next
652
340
  }
653
- @on_unsuback.call(to_unsub) unless @on_unsuback.nil?
654
341
  end
655
342
 
656
- def handle_publish(packet)
657
- case packet.qos
658
- when 0
659
- when 1
660
- send_puback(packet.id)
661
- when 2
662
- send_pubrec(packet.id)
343
+ def downgrade_version
344
+ PahoMqtt.logger.debug("Unable to connect to the server with the version #{@mqtt_version}, trying 3.1") if PahoMqtt.logger?
345
+ if @mqtt_version != "3.1"
346
+ @mqtt_version = "3.1"
347
+ connect(@host, @port, @keep_alive)
663
348
  else
664
- @logger.error("The packet qos value is invalid in publish.") if @logger.is_a?(Logger)
665
- raise PacketException
349
+ raise "Unsupported MQTT version"
666
350
  end
667
-
668
- @on_message.call(packet) unless @on_message.nil?
669
- @registered_callback.assoc(packet.topic).last.call(packet) if @registered_callback.any? { |pair| pair.first == packet.topic}
670
- end
671
-
672
- def handle_puback(packet)
673
- @puback_mutex.synchronize{
674
- @waiting_puback.delete_if { |pck| pck[:id] == packet.id }
675
- }
676
- @on_puback.call(packet) unless @on_puback.nil?
677
- end
678
-
679
- def handle_pubrec(packet)
680
- @pubrec_mutex.synchronize {
681
- @waiting_pubrec.delete_if { |pck| pck[:id] == packet.id }
682
- }
683
- send_pubrel(packet.id)
684
- @on_pubrec.call(packet) unless @on_pubrec.nil?
685
351
  end
686
352
 
687
- def handle_pubrel(packet)
688
- @pubrel_mutex.synchronize {
689
- @waiting_pubrel.delete_if { |pck| pck[:id] == packet.id }
690
- }
691
- send_pubcomp(packet.id)
692
- @on_pubrel.call(packet) unless @on_pubrel.nil?
693
- end
694
-
695
- def handle_pubcomp(packet)
696
- @pubcomp_mutex.synchronize {
697
- @waiting_pubcomp.delete_if { |pck| pck[:id] == packet.id }
698
- }
699
- @on_pubcomp.call(packet) unless @on_pubcomp.nil?
700
- end
701
-
702
- def handle_connack_error(return_code)
703
- case return_code
704
- when 0x01
705
- @logger.debug("Unable to connect to the server with the version #{@mqtt_version}, trying 3.1") if @logger.is_a?(Logger)
706
- if @mqtt_version != "3.1"
707
- @mqtt_version = "3.1"
708
- connect(@host, @port, @keep_alive)
709
- end
710
- when 0x02
711
- @logger.warn("Client Identifier is correct but not allowed by remote server.") if @logger.is_a?(Logger)
712
- MQTT_ERR_FAIL
713
- when 0x03
714
- @logger.warn("Connection established but MQTT service unvailable on remote server.") if @logger.is_a?(Logger)
715
- MQTT_ERR_FAIL
716
- when 0x04
717
- @logger.warn("User name or user password is malformed.") if @logger.is_a?(Logger)
718
- MQTT_ERR_FAIL
719
- when 0x05
720
- @logger.warn("Client is not authorized to connect to the server.") if @logger.is_a?(Logger)
721
- MQTT_ERR_FAIL
722
- end
723
- end
724
-
725
- def send_packet(packet)
726
- begin
727
- @socket.write(packet.to_s) unless @socket.nil? || @socket.closed?
728
- @logger.info("A packet #{packet.class} have been sent.") if @logger.is_a?(Logger)
729
- @last_ping_req = Time.now
730
- MQTT_ERR_SUCCESS
731
- rescue ::Exception => exp
732
- disconnect(false)
733
- if @persistent
734
- reconnect
735
- else
736
- @logger.error("Trying to right a packet on a nil socket.") if @logger.is_a?(Logger)
737
- raise(exp)
738
- end
739
- end
740
- end
741
-
742
- def append_to_writing(packet)
743
- @writing_mutex.synchronize {
744
- @writing_queue.push(packet)
745
- }
746
- MQTT_ERR_SUCCESS
747
- end
748
-
749
- def send_connect
750
- packet = PahoMqtt::Packet::Connect.new(
751
- :version => @mqtt_version,
752
- :clean_session => @clean_session,
753
- :keep_alive => @keep_alive,
754
- :client_id => @client_id,
755
- :username => @username,
756
- :password => @password,
757
- :will_topic => @will_topic,
758
- :will_payload => @will_payload,
759
- :will_qos => @will_qos,
760
- :will_retain => @will_retain
761
- )
762
- send_packet(packet)
763
- MQTT_ERR_SUCCESS
764
- end
765
-
766
- def send_disconnect
767
- packet = PahoMqtt::Packet::Disconnect.new
768
- send_packet(packet)
769
- MQTT_ERR_SUCCESS
770
- end
771
-
772
- def send_pingreq
773
- packet = PahoMqtt::Packet::Pingreq.new
774
- @logger.debug("Checking if server is still alive.") if @logger.is_a?(Logger)
775
- send_packet(packet)
776
- MQTT_ERR_SUCCESS
777
- end
778
-
779
- def send_subscribe(topics)
780
- unless valid_topics?(topics) == MQTT_ERR_FAIL
781
- new_id = next_packet_id
782
- packet = PahoMqtt::Packet::Subscribe.new(
783
- :id => new_id,
784
- :topics => topics
785
- )
786
- append_to_writing(packet)
787
- @suback_mutex.synchronize {
788
- @waiting_suback.push({ :id => new_id, :packet => packet, :timestamp => Time.now })
789
- }
790
- MQTT_ERR_SUCCESS
353
+ def build_pubsub
354
+ if @subscriber.nil?
355
+ @subscriber = Subscriber.new(@sender)
791
356
  else
792
- disconnect(false)
793
- raise ProtocolViolation
357
+ @subscriber.sender = @sender
358
+ @subscriber.config_subscription(next_packet_id)
794
359
  end
795
- end
796
-
797
- def send_unsubscribe(topics)
798
- unless valid_topics?(topics) == MQTT_ERR_FAIL
799
- new_id = next_packet_id
800
- packet = PahoMqtt::Packet::Unsubscribe.new(
801
- :id => new_id,
802
- :topics => topics
803
- )
804
-
805
- append_to_writing(packet)
806
- @unsuback_mutex.synchronize {
807
- @waiting_unsuback.push({:id => new_id, :packet => packet, :timestamp => Time.now})
808
- }
809
- MQTT_ERR_SUCCESS
360
+ if @publisher.nil?
361
+ @publisher = Publisher.new(@sender)
810
362
  else
811
- disconnect(false)
812
- raise ProtocolViolation
363
+ @publisher.sender = @sender
364
+ @publisher.config_all_message_queue
813
365
  end
366
+ @handler.config_pubsub(@publisher, @subscriber)
367
+ @sender.flush_waiting_packet(true)
814
368
  end
815
369
 
816
- def send_publish(topic, payload, retain, qos)
817
- new_id = next_packet_id
818
- packet = PahoMqtt::Packet::Publish.new(
819
- :id => new_id,
820
- :topic => topic,
821
- :payload => payload,
822
- :retain => retain,
823
- :qos => qos
824
- )
825
-
826
- append_to_writing(packet)
827
-
828
- case qos
829
- when 1
830
- @puback_mutex.synchronize{
831
- @waiting_puback.push({:id => new_id, :packet => packet, :timestamp => Time.now})
832
- }
833
- when 2
834
- @pubrec_mutex.synchronize{
835
- @waiting_pubrec.push({:id => new_id, :packet => packet, :timestamp => Time.now})
836
- }
370
+ def init_connection
371
+ unless reconnect?
372
+ @connection_helper = ConnectionHelper.new(@host, @port, @ssl, @ssl_context, @ack_timeout)
373
+ @connection_helper.handler = @handler
374
+ @sender = @connection_helper.sender
837
375
  end
838
- MQTT_ERR_SUCCESS
376
+ @connection_helper.setup_connection
839
377
  end
840
378
 
841
- def send_puback(packet_id)
842
- packet = PahoMqtt::Packet::Puback.new(
843
- :id => packet_id
844
- )
845
-
846
- append_to_writing(packet)
847
- MQTT_ERR_SUCCESS
848
- end
849
-
850
- def send_pubrec(packet_id)
851
- packet = PahoMqtt::Packet::Pubrec.new(
852
- :id => packet_id
853
- )
854
-
855
- append_to_writing(packet)
856
-
857
- @pubrel_mutex.synchronize{
858
- @waiting_pubrel.push({:id => packet_id , :packet => packet, :timestamp => Time.now})
859
- }
860
- MQTT_ERR_SUCCESS
379
+ def session_params
380
+ {:version => @mqtt_version,
381
+ :clean_session => @clean_session,
382
+ :keep_alive => @keep_alive,
383
+ :client_id => @client_id,
384
+ :username => @username,
385
+ :password => @password,
386
+ :will_topic => @will_topic,
387
+ :will_payload => @will_payload,
388
+ :will_qos => @will_qos,
389
+ :will_retain => @will_retain}
861
390
  end
862
-
863
- def send_pubrel(packet_id)
864
- packet = PahoMqtt::Packet::Pubrel.new(
865
- :id => packet_id
866
- )
867
-
868
- append_to_writing(packet)
869
-
870
- @pubcomp_mutex.synchronize{
871
- @waiting_pubcomp.push({:id => packet_id, :packet => packet, :timestamp => Time.now})
872
- }
873
- MQTT_ERR_SUCCESS
874
- end
875
-
876
- def send_pubcomp(packet_id)
877
- packet = PahoMqtt::Packet::Pubcomp.new(
878
- :id => packet_id
879
- )
880
391
 
881
- append_to_writing(packet)
882
- MQTT_ERR_SUCCESS
883
- end
884
-
885
- def valid_topics?(topics)
886
- unless topics.length == 0
887
- topics.map do |topic|
888
- return MQTT_ERR_FAIL if topic.first == ""
889
- end
890
- else
891
- MQTT_ERR_FAIL
892
- end
893
- MQTT_ERR_SUCCESS
894
- end
895
-
896
- def match_filter(topics, filters)
897
- if topics.is_a?(String) && filters.is_a?(String)
898
- topic = topics.split('/')
899
- filter = filters.split('/')
900
- else
901
- @logger.error("Topics and filters are not found as String while matching topics to filter.") if @logger.is_a?(Logger)
902
- raise ParameterException
903
- end
904
-
905
- rc = false
906
- index = 0
907
-
908
- while index < [topic.length, filter.length].max do
909
- if topic[index].nil? || filter[index].nil?
910
- break
911
- elsif filter[index] == '#' && index == (filter.length - 1)
912
- rc = true
913
- break
914
- elsif filter[index] == topic[index] || filter[index] == '+'
915
- index = index + 1
916
- else
917
- break
918
- end
919
- end
920
- rc ||= (index == [topic.length, filter.length].max)
392
+ def check_persistence
393
+ disconnect(false)
394
+ @persistent
921
395
  end
922
396
  end
923
397
  end