mqtt-ccutrer 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b93bce8e1cc0e50a0161ac2447fbff2c293ad89f64dc98cb848e20026e754e3
4
- data.tar.gz: 4b480e5ffb4df550469d112274f6c831b96f4a2118bb29e32940156b9dccce58
3
+ metadata.gz: c5586204c6a2b82ab49ccd6f6fc201612985566ac2b0a6580fc0144c83a3b06c
4
+ data.tar.gz: d38d819d114df28b8dbcbbd357e45fc213ba8932f46dbdcc8f368f8e69e627df
5
5
  SHA512:
6
- metadata.gz: 3227b778e824d891845cab272ffe73f875ffe2de3440a56e1a3f73bffb5d6fa9edb41844665aead5411276c0241ef4f176fc369b98d1398c54bbdf78dc9103d9
7
- data.tar.gz: 51254e256507a8c01dd9197266eed692ccad64c3a7b0baa8bbc3ec50350ea712bc2a8bbfd1b575fca4527d5fe4da0a706f33aa998a932aca8a517f5af40b6721
6
+ metadata.gz: 8025eeb04ed80c513422f7ebf57b45b16298eb4c226373ea179fb9cbea87be474c2befbd48a26ba4c912bddafa6a21e7f35c6ced815a6c634a7b31355b24c15f
7
+ data.tar.gz: 41c23b03d0911fffee6aad9abf50df5c1ac0203a3ab44cf16354120b5b6b6687ec62b8704951be4c14f41f98db00d450e464133a983604e40f9a885ad904d55f
data/README.md CHANGED
@@ -1,5 +1,3 @@
1
- [![Build Status](https://travis-ci.org/njh/ruby-mqtt.svg)](https://travis-ci.org/ccutrer/ruby-mqtt)
2
-
3
1
  ruby-mqtt
4
2
  =========
5
3
 
@@ -84,6 +82,15 @@ client.ca_file = path_to('root-ca.pem')
84
82
  client.connect
85
83
  ~~~
86
84
 
85
+ The default timeout when opening a TCP Socket is 30 seconds. To specify it explicitly, use 'connect_timeout =>':
86
+
87
+ ~~~ ruby
88
+ client = MQTT::Client.connect(
89
+ :host => 'myserver.example.com',
90
+ :connect_timeout => 15
91
+ )
92
+ ~~~
93
+
87
94
  The connection can either be made without the use of a block:
88
95
 
89
96
  ~~~ ruby
data/lib/mqtt/client.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- autoload :OpenSSL, 'openssl'
4
- autoload :SecureRandom, 'securerandom'
5
- autoload :URI, 'uri'
3
+ autoload :OpenSSL, "openssl"
4
+ autoload :SecureRandom, "securerandom"
5
+ autoload :URI, "uri"
6
6
 
7
7
  # Client class for talking to an MQTT server
8
8
  module MQTT
@@ -26,6 +26,9 @@ module MQTT
26
26
  # @see OpenSSL::SSL::SSLContext::METHODS
27
27
  attr_accessor :ssl
28
28
 
29
+ # Set to false to skip tls hostname verification
30
+ attr_accessor :verify_host
31
+
29
32
  # Time (in seconds) between pings to remote server (default is 15 seconds)
30
33
  attr_accessor :keep_alive
31
34
 
@@ -38,19 +41,25 @@ module MQTT
38
41
  # Number of seconds to wait for acknowledgement packets (default is 5 seconds)
39
42
  attr_accessor :ack_timeout
40
43
 
44
+ # Number of seconds to connect to the server (default is 90 seconds)
45
+ attr_accessor :connect_timeout
46
+
41
47
  # How many times to attempt re-sending packets that weren't acknowledged
42
48
  # (default is 5) before giving up
43
49
  attr_accessor :resend_limit
44
50
 
45
51
  # How many attempts to re-establish a connection after it drops before
46
- # giving up (default 5)
52
+ # giving up (default 5); nil for unlimited retries
47
53
  attr_accessor :reconnect_limit
48
54
 
49
55
  # How long to wait between re-connection attempts (exponential - i.e.
50
56
  # immediately after first drop, then 5s, then 25s, then 125s, etc.
51
- # when theis value defaults to 5)
57
+ # when this value defaults to 5)
52
58
  attr_accessor :reconnect_backoff
53
59
 
60
+ # the longest amount of time to wait before attempting a reconnect
61
+ attr_accessor :reconnect_backoff_max
62
+
54
63
  # Username to authenticate to the server with
55
64
  attr_accessor :username
56
65
 
@@ -73,21 +82,24 @@ module MQTT
73
82
  ATTR_DEFAULTS = {
74
83
  host: nil,
75
84
  port: nil,
76
- version: '3.1.1',
85
+ version: "3.1.1",
77
86
  keep_alive: 15,
78
87
  clean_session: true,
79
88
  client_id: nil,
80
89
  ack_timeout: 5,
90
+ connect_timeout: 30,
81
91
  resend_limit: 5,
82
92
  reconnect_limit: 5,
83
- reconnect_backoff: 5,
93
+ reconnect_backoff: 2,
94
+ reconnect_backoff_max: 30,
84
95
  username: nil,
85
96
  password: nil,
86
97
  will_topic: nil,
87
98
  will_payload: nil,
88
99
  will_qos: 0,
89
100
  will_retain: false,
90
- ssl: false
101
+ ssl: false,
102
+ verify_host: true
91
103
  }.freeze
92
104
 
93
105
  # Create and connect a new MQTT Client
@@ -100,15 +112,15 @@ module MQTT
100
112
  # # do stuff here
101
113
  # end
102
114
  #
103
- def self.connect(*args, &block)
115
+ def self.connect(*args, &)
104
116
  client = MQTT::Client.new(*args)
105
- client.connect(&block)
117
+ client.connect(&)
106
118
  client
107
119
  end
108
120
 
109
121
  # Generate a random client identifier
110
122
  # (using the characters 0-9 and a-z)
111
- def self.generate_client_id(prefix = 'ruby', length = 16)
123
+ def self.generate_client_id(prefix = "ruby", length = 16)
112
124
  "#{prefix}#{SecureRandom.alphanumeric(length).downcase}"
113
125
  end
114
126
 
@@ -130,11 +142,14 @@ module MQTT
130
142
  # client = MQTT::Client.new('myserver.example.com', 18830)
131
143
  # client = MQTT::Client.new(host: 'myserver.example.com')
132
144
  # client = MQTT::Client.new(host: 'myserver.example.com', keep_alive: 30)
145
+ # client = MQTT::Client.new(uri: 'mqtt://myserver.example.com', keep_alive: 30)
133
146
  #
134
147
  def initialize(host = nil, port = nil, **attributes)
148
+ host = attributes.delete(:uri) if attributes.key?(:uri)
149
+
135
150
  # Set server URI from environment if present
136
- if host.nil? && port.nil? && attributes.empty? && ENV['MQTT_SERVER']
137
- attributes.merge!(parse_uri(ENV['MQTT_SERVER']))
151
+ if host.nil? && port.nil? && attributes.empty? && ENV["MQTT_SERVER"]
152
+ attributes.merge!(parse_uri(ENV["MQTT_SERVER"]))
138
153
  end
139
154
 
140
155
  if host
@@ -196,13 +211,13 @@ module MQTT
196
211
  # Set a path to a file containing a PEM-format client private key
197
212
  def key_file=(*args)
198
213
  path, passphrase = args.flatten
199
- ssl_context.key = OpenSSL::PKey::RSA.new(File.open(path), passphrase)
214
+ ssl_context.key = OpenSSL::PKey.read(File.binread(path), passphrase)
200
215
  end
201
216
 
202
217
  # Set to a PEM-format client private key
203
218
  def key=(*args)
204
219
  cert, passphrase = args.flatten
205
- ssl_context.key = OpenSSL::PKey::RSA.new(cert, passphrase)
220
+ ssl_context.key = OpenSSL::PKey.read(cert, passphrase)
206
221
  end
207
222
 
208
223
  # Set a path to a file containing a PEM-format CA certificate and enable peer verification
@@ -232,13 +247,13 @@ module MQTT
232
247
  end
233
248
 
234
249
  if @client_id.nil? || @client_id.empty?
235
- raise 'Must provide a client_id if clean_session is set to false' unless @clean_session
250
+ raise "Must provide a client_id if clean_session is set to false" unless @clean_session
236
251
 
237
252
  # Empty client id is not allowed for version 3.1.0
238
- @client_id = MQTT::Client.generate_client_id if @version == '3.1.0'
253
+ @client_id = MQTT::Client.generate_client_id if @version == "3.1.0"
239
254
  end
240
255
 
241
- raise ArgumentError, 'No MQTT server host set when attempting to connect' if @host.nil?
256
+ raise ArgumentError, "No MQTT server host set when attempting to connect" if @host.nil?
242
257
 
243
258
  connect_internal
244
259
 
@@ -347,12 +362,12 @@ module MQTT
347
362
  # Publish a message on a particular topic to the MQTT server.
348
363
  def publish(topics, payload = nil, retain: false, qos: 0)
349
364
  if topics.is_a?(Hash) && !payload.nil?
350
- raise ArgumentError, 'Payload cannot be passed if passing a hash for topics and payloads'
365
+ raise ArgumentError, "Payload cannot be passed if passing a hash for topics and payloads"
351
366
  end
352
367
  raise NotConnectedException unless connected?
353
368
 
354
369
  if @batch_publish && qos != 0
355
- values = @batch_publish[{ retain: retain, qos: qos }] ||= {}
370
+ values = @batch_publish[{ retain:, qos: }] ||= {}
356
371
  if topics.is_a?(Hash)
357
372
  values.merge!(topics)
358
373
  else
@@ -366,14 +381,14 @@ module MQTT
366
381
  topics = { topics => payload } unless topics.is_a?(Hash)
367
382
 
368
383
  topics.each do |(topic, topic_payload)|
369
- raise ArgumentError, 'Topic name cannot be nil' if topic.nil?
370
- raise ArgumentError, 'Topic name cannot be empty' if topic.empty?
384
+ raise ArgumentError, "Topic name cannot be nil" if topic.nil?
385
+ raise ArgumentError, "Topic name cannot be empty" if topic.empty?
371
386
 
372
387
  packet = MQTT::Packet::Publish.new(
373
388
  id: next_packet_id,
374
- qos: qos,
375
- retain: retain,
376
- topic: topic,
389
+ qos:,
390
+ retain:,
391
+ topic:,
377
392
  payload: topic_payload
378
393
  )
379
394
 
@@ -408,7 +423,7 @@ module MQTT
408
423
 
409
424
  packet = MQTT::Packet::Subscribe.new(
410
425
  id: next_packet_id,
411
- topics: topics
426
+ topics:
412
427
  )
413
428
  token = register_for_ack(packet) if wait_for_ack
414
429
  send_packet(packet)
@@ -419,10 +434,10 @@ module MQTT
419
434
  def unsubscribe(*topics, wait_for_ack: false)
420
435
  raise NotConnectedException unless connected?
421
436
 
422
- topics = topics.first if topics.is_a?(Enumerable) && topics.count == 1
437
+ topics = topics.first if topics.is_a?(Enumerable) && topics.one?
423
438
 
424
439
  packet = MQTT::Packet::Unsubscribe.new(
425
- topics: topics,
440
+ topics:,
426
441
  id: next_packet_id
427
442
  )
428
443
  token = register_for_ack(packet) if wait_for_ack
@@ -448,18 +463,18 @@ module MQTT
448
463
  packet = @read_queue.pop
449
464
  if packet.is_a?(Array) && packet.last >= loop_start
450
465
  e = packet.first
451
- e.set_backtrace((e.backtrace || []) + ['<from MQTT worker thread>'] + caller)
466
+ e.set_backtrace((e.backtrace || []) + ["<from MQTT worker thread>"] + caller)
452
467
  raise e
453
468
  end
454
469
  next unless packet.is_a?(Packet)
455
470
 
456
471
  unless block_given?
457
- puback_packet(packet) if packet.qos > 0
472
+ puback_packet(packet) if packet.qos.positive?
458
473
  return packet
459
474
  end
460
475
 
461
476
  yield packet
462
- puback_packet(packet) if packet.qos > 0
477
+ puback_packet(packet) if packet.qos.positive?
463
478
  end
464
479
  end
465
480
 
@@ -481,10 +496,11 @@ module MQTT
481
496
  private
482
497
 
483
498
  PendingAck = Struct.new(:packet, :queue, :timeout_at, :send_count)
499
+ private_constant :PendingAck
484
500
 
485
501
  def connect_internal
486
502
  # Create network socket
487
- tcp_socket = TCPSocket.new(@host, @port)
503
+ tcp_socket = open_tcp_socket
488
504
 
489
505
  if @ssl
490
506
  # Set the protocol version
@@ -497,6 +513,8 @@ module MQTT
497
513
  @socket.hostname = @host if @socket.respond_to?(:hostname=)
498
514
 
499
515
  @socket.connect
516
+
517
+ @socket.post_connection_check(@host) if @verify_host
500
518
  else
501
519
  @socket = tcp_socket
502
520
  end
@@ -556,13 +574,14 @@ module MQTT
556
574
 
557
575
  retries = 0
558
576
  begin
559
- connect_internal unless @reconnect_limit == 0
577
+ connect_internal unless @reconnect_limit.zero?
560
578
  rescue
561
579
  @socket&.close
562
580
  @socket = nil
581
+ retries += 1
563
582
 
564
- if (retries += 1) < @reconnect_limit
565
- sleep @reconnect_backoff ** retries
583
+ if @reconnect_limit.nil? || retries < @reconnect_limit
584
+ sleep [@reconnect_backoff**retries, @reconnect_backoff_max].min
566
585
  retry
567
586
  end
568
587
  end
@@ -582,7 +601,7 @@ module MQTT
582
601
  end
583
602
 
584
603
  begin
585
- if @on_reconnect&.arity == 0
604
+ if @on_reconnect&.arity&.zero?
586
605
  @on_reconnect.call
587
606
  else
588
607
  @on_reconnect&.call(@connack)
@@ -624,7 +643,7 @@ module MQTT
624
643
  @acks_mutex.synchronize do
625
644
  if @acks.empty?
626
645
  # just need to wake up the read thread to set up the timeout for this packet
627
- @wake_up_pipe[1].write('z')
646
+ @wake_up_pipe[1].write("z")
628
647
  end
629
648
  @acks[packet.id] = PendingAck.new(packet, queue, timeout_at, 1)
630
649
  end
@@ -683,7 +702,7 @@ module MQTT
683
702
  return
684
703
  end
685
704
  # timed out, or simple re-send
686
- @wake_up_pipe[1].write('z') if @acks.first.first == packet.id
705
+ @wake_up_pipe[1].write("z") if @acks.first.first == packet.id
687
706
  pending_ack.timeout_at = current_time + @ack_timeout
688
707
  packet.duplicate = true
689
708
  send_packet(packet)
@@ -711,7 +730,7 @@ module MQTT
711
730
  end
712
731
 
713
732
  def handle_keep_alives
714
- return unless @keep_alive && @keep_alive > 0
733
+ return unless @keep_alive&.positive?
715
734
 
716
735
  current_time_local = current_time
717
736
  if current_time_local >= @last_packet_sent_at + @keep_alive && !@keep_alive_sent
@@ -756,20 +775,20 @@ module MQTT
756
775
  def parse_uri(uri)
757
776
  uri = URI.parse(uri) unless uri.is_a?(URI)
758
777
  ssl = case uri.scheme
759
- when 'mqtt'
778
+ when "mqtt"
760
779
  false
761
- when 'mqtts'
780
+ when "mqtts"
762
781
  true
763
782
  else
764
- raise 'Only the mqtt:// and mqtts:// schemes are supported'
783
+ raise "Only the mqtt:// and mqtts:// schemes are supported"
765
784
  end
766
785
 
767
786
  {
768
787
  host: uri.host,
769
788
  port: uri.port || nil,
770
- username: uri.user ? URI::Parser.new.unescape(uri.user) : nil,
771
- password: uri.password ? URI::Parser.new.unescape(uri.password) : nil,
772
- ssl: ssl
789
+ username: uri.user ? URI::RFC2396_PARSER.unescape(uri.user) : nil,
790
+ password: uri.password ? URI::RFC2396_PARSER.unescape(uri.password) : nil,
791
+ ssl:
773
792
  }
774
793
  end
775
794
 
@@ -778,5 +797,18 @@ module MQTT
778
797
  @last_packet_id = 1 if @last_packet_id > 0xffff
779
798
  @last_packet_id
780
799
  end
800
+
801
+ def open_tcp_socket
802
+ return TCPSocket.new @host, @port, connect_timeout: @connect_timeout if RUBY_VERSION.to_f >= 3.0
803
+
804
+ begin
805
+ Timeout.timeout(@connect_timeout) do
806
+ return TCPSocket.new(@host, @port)
807
+ end
808
+ rescue Timeout::Error
809
+ raise (defined?(IO::TimeoutError) ? IO::TimeoutError : Errno::ETIMEDOUT),
810
+ "Connection timed out for \"#{@host}\" port #{@port}"
811
+ end
812
+ end
781
813
  end
782
814
  end