mqtt-ccutrer 1.0.3 → 1.1.1
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 +4 -4
- data/README.md +9 -2
- data/lib/mqtt/client.rb +76 -48
- data/lib/mqtt/packet.rb +113 -113
- data/lib/mqtt/proxy.rb +5 -5
- data/lib/mqtt/sn/packet.rb +80 -80
- data/lib/mqtt/version.rb +1 -1
- data/lib/mqtt-ccutrer.rb +1 -1
- data/lib/mqtt.rb +8 -8
- metadata +15 -131
- data/spec/zz_client_integration_spec.rb +0 -180
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c0a41d0227405118d24d483405143974e08a6606f41990eb74e3061aeac0b8c6
|
|
4
|
+
data.tar.gz: bc82c7c0fed1835e290639c38d948fea328ef744df28536e9cd828777013594a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c630b2d5554bb36770598a0b3ae6ff470482ed43d1f6accf92eea9fc733efcd5d37dc595fe8b9dc982418612cd0a2a4ae7abc1ab22a6e8af137ee3c46b20c0a
|
|
7
|
+
data.tar.gz: 0b2167ca3b631389b650dab69acd1c272a935766bc4d5eb8b8d6848c5316f2a6ad930833b409c2dbbc3d2c82abca929ca4d9bb7128231bfb84daaa7322957006
|
data/README.md
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
[](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,
|
|
4
|
-
autoload :SecureRandom,
|
|
5
|
-
autoload :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,6 +41,9 @@ 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
|
|
@@ -76,11 +82,12 @@ module MQTT
|
|
|
76
82
|
ATTR_DEFAULTS = {
|
|
77
83
|
host: nil,
|
|
78
84
|
port: nil,
|
|
79
|
-
version:
|
|
85
|
+
version: "3.1.1",
|
|
80
86
|
keep_alive: 15,
|
|
81
87
|
clean_session: true,
|
|
82
88
|
client_id: nil,
|
|
83
89
|
ack_timeout: 5,
|
|
90
|
+
connect_timeout: 30,
|
|
84
91
|
resend_limit: 5,
|
|
85
92
|
reconnect_limit: 5,
|
|
86
93
|
reconnect_backoff: 2,
|
|
@@ -91,7 +98,8 @@ module MQTT
|
|
|
91
98
|
will_payload: nil,
|
|
92
99
|
will_qos: 0,
|
|
93
100
|
will_retain: false,
|
|
94
|
-
ssl: false
|
|
101
|
+
ssl: false,
|
|
102
|
+
verify_host: true
|
|
95
103
|
}.freeze
|
|
96
104
|
|
|
97
105
|
# Create and connect a new MQTT Client
|
|
@@ -104,15 +112,15 @@ module MQTT
|
|
|
104
112
|
# # do stuff here
|
|
105
113
|
# end
|
|
106
114
|
#
|
|
107
|
-
def self.connect(*args, &
|
|
115
|
+
def self.connect(*args, &)
|
|
108
116
|
client = MQTT::Client.new(*args)
|
|
109
|
-
client.connect(&
|
|
117
|
+
client.connect(&)
|
|
110
118
|
client
|
|
111
119
|
end
|
|
112
120
|
|
|
113
121
|
# Generate a random client identifier
|
|
114
122
|
# (using the characters 0-9 and a-z)
|
|
115
|
-
def self.generate_client_id(prefix =
|
|
123
|
+
def self.generate_client_id(prefix = "ruby", length = 16)
|
|
116
124
|
"#{prefix}#{SecureRandom.alphanumeric(length).downcase}"
|
|
117
125
|
end
|
|
118
126
|
|
|
@@ -140,8 +148,8 @@ module MQTT
|
|
|
140
148
|
host = attributes.delete(:uri) if attributes.key?(:uri)
|
|
141
149
|
|
|
142
150
|
# Set server URI from environment if present
|
|
143
|
-
if host.nil? && port.nil? && attributes.empty? && ENV[
|
|
144
|
-
attributes.merge!(parse_uri(ENV[
|
|
151
|
+
if host.nil? && port.nil? && attributes.empty? && ENV["MQTT_SERVER"]
|
|
152
|
+
attributes.merge!(parse_uri(ENV["MQTT_SERVER"]))
|
|
145
153
|
end
|
|
146
154
|
|
|
147
155
|
if host
|
|
@@ -203,13 +211,13 @@ module MQTT
|
|
|
203
211
|
# Set a path to a file containing a PEM-format client private key
|
|
204
212
|
def key_file=(*args)
|
|
205
213
|
path, passphrase = args.flatten
|
|
206
|
-
ssl_context.key = OpenSSL::PKey
|
|
214
|
+
ssl_context.key = OpenSSL::PKey.read(File.binread(path), passphrase)
|
|
207
215
|
end
|
|
208
216
|
|
|
209
217
|
# Set to a PEM-format client private key
|
|
210
218
|
def key=(*args)
|
|
211
219
|
cert, passphrase = args.flatten
|
|
212
|
-
ssl_context.key = OpenSSL::PKey
|
|
220
|
+
ssl_context.key = OpenSSL::PKey.read(cert, passphrase)
|
|
213
221
|
end
|
|
214
222
|
|
|
215
223
|
# Set a path to a file containing a PEM-format CA certificate and enable peer verification
|
|
@@ -239,13 +247,13 @@ module MQTT
|
|
|
239
247
|
end
|
|
240
248
|
|
|
241
249
|
if @client_id.nil? || @client_id.empty?
|
|
242
|
-
raise
|
|
250
|
+
raise "Must provide a client_id if clean_session is set to false" unless @clean_session
|
|
243
251
|
|
|
244
252
|
# Empty client id is not allowed for version 3.1.0
|
|
245
|
-
@client_id = MQTT::Client.generate_client_id if @version ==
|
|
253
|
+
@client_id = MQTT::Client.generate_client_id if @version == "3.1.0"
|
|
246
254
|
end
|
|
247
255
|
|
|
248
|
-
raise ArgumentError,
|
|
256
|
+
raise ArgumentError, "No MQTT server host set when attempting to connect" if @host.nil?
|
|
249
257
|
|
|
250
258
|
connect_internal
|
|
251
259
|
|
|
@@ -354,12 +362,12 @@ module MQTT
|
|
|
354
362
|
# Publish a message on a particular topic to the MQTT server.
|
|
355
363
|
def publish(topics, payload = nil, retain: false, qos: 0)
|
|
356
364
|
if topics.is_a?(Hash) && !payload.nil?
|
|
357
|
-
raise ArgumentError,
|
|
365
|
+
raise ArgumentError, "Payload cannot be passed if passing a hash for topics and payloads"
|
|
358
366
|
end
|
|
359
367
|
raise NotConnectedException unless connected?
|
|
360
368
|
|
|
361
369
|
if @batch_publish && qos != 0
|
|
362
|
-
values = @batch_publish[{ retain
|
|
370
|
+
values = @batch_publish[{ retain:, qos: }] ||= {}
|
|
363
371
|
if topics.is_a?(Hash)
|
|
364
372
|
values.merge!(topics)
|
|
365
373
|
else
|
|
@@ -373,14 +381,14 @@ module MQTT
|
|
|
373
381
|
topics = { topics => payload } unless topics.is_a?(Hash)
|
|
374
382
|
|
|
375
383
|
topics.each do |(topic, topic_payload)|
|
|
376
|
-
raise ArgumentError,
|
|
377
|
-
raise ArgumentError,
|
|
384
|
+
raise ArgumentError, "Topic name cannot be nil" if topic.nil?
|
|
385
|
+
raise ArgumentError, "Topic name cannot be empty" if topic.empty?
|
|
378
386
|
|
|
379
387
|
packet = MQTT::Packet::Publish.new(
|
|
380
388
|
id: next_packet_id,
|
|
381
|
-
qos
|
|
382
|
-
retain
|
|
383
|
-
topic
|
|
389
|
+
qos:,
|
|
390
|
+
retain:,
|
|
391
|
+
topic:,
|
|
384
392
|
payload: topic_payload
|
|
385
393
|
)
|
|
386
394
|
|
|
@@ -415,7 +423,7 @@ module MQTT
|
|
|
415
423
|
|
|
416
424
|
packet = MQTT::Packet::Subscribe.new(
|
|
417
425
|
id: next_packet_id,
|
|
418
|
-
topics:
|
|
426
|
+
topics:
|
|
419
427
|
)
|
|
420
428
|
token = register_for_ack(packet) if wait_for_ack
|
|
421
429
|
send_packet(packet)
|
|
@@ -426,10 +434,10 @@ module MQTT
|
|
|
426
434
|
def unsubscribe(*topics, wait_for_ack: false)
|
|
427
435
|
raise NotConnectedException unless connected?
|
|
428
436
|
|
|
429
|
-
topics = topics.first if topics.is_a?(Enumerable) && topics.
|
|
437
|
+
topics = topics.first if topics.is_a?(Enumerable) && topics.one?
|
|
430
438
|
|
|
431
439
|
packet = MQTT::Packet::Unsubscribe.new(
|
|
432
|
-
topics
|
|
440
|
+
topics:,
|
|
433
441
|
id: next_packet_id
|
|
434
442
|
)
|
|
435
443
|
token = register_for_ack(packet) if wait_for_ack
|
|
@@ -455,18 +463,18 @@ module MQTT
|
|
|
455
463
|
packet = @read_queue.pop
|
|
456
464
|
if packet.is_a?(Array) && packet.last >= loop_start
|
|
457
465
|
e = packet.first
|
|
458
|
-
e.set_backtrace((e.backtrace || []) + [
|
|
466
|
+
e.set_backtrace((e.backtrace || []) + ["<from MQTT worker thread>"] + caller)
|
|
459
467
|
raise e
|
|
460
468
|
end
|
|
461
469
|
next unless packet.is_a?(Packet)
|
|
462
470
|
|
|
463
471
|
unless block_given?
|
|
464
|
-
puback_packet(packet) if packet.qos
|
|
472
|
+
puback_packet(packet) if packet.qos.positive?
|
|
465
473
|
return packet
|
|
466
474
|
end
|
|
467
475
|
|
|
468
476
|
yield packet
|
|
469
|
-
puback_packet(packet) if packet.qos
|
|
477
|
+
puback_packet(packet) if packet.qos.positive?
|
|
470
478
|
end
|
|
471
479
|
end
|
|
472
480
|
|
|
@@ -488,10 +496,11 @@ module MQTT
|
|
|
488
496
|
private
|
|
489
497
|
|
|
490
498
|
PendingAck = Struct.new(:packet, :queue, :timeout_at, :send_count)
|
|
499
|
+
private_constant :PendingAck
|
|
491
500
|
|
|
492
501
|
def connect_internal
|
|
493
502
|
# Create network socket
|
|
494
|
-
tcp_socket =
|
|
503
|
+
tcp_socket = open_tcp_socket
|
|
495
504
|
|
|
496
505
|
if @ssl
|
|
497
506
|
# Set the protocol version
|
|
@@ -504,6 +513,8 @@ module MQTT
|
|
|
504
513
|
@socket.hostname = @host if @socket.respond_to?(:hostname=)
|
|
505
514
|
|
|
506
515
|
@socket.connect
|
|
516
|
+
|
|
517
|
+
@socket.post_connection_check(@host) if @verify_host
|
|
507
518
|
else
|
|
508
519
|
@socket = tcp_socket
|
|
509
520
|
end
|
|
@@ -561,28 +572,32 @@ module MQTT
|
|
|
561
572
|
should_exit = Thread.current == @read_thread
|
|
562
573
|
@read_thread = @write_thread = nil
|
|
563
574
|
|
|
575
|
+
# Clear pending acks from the old connection. Their packets were
|
|
576
|
+
# sent (or queued) on a now-dead socket, so the broker will never
|
|
577
|
+
# acknowledge them. Unblock any threads waiting in wait_for_ack.
|
|
578
|
+
@acks_mutex.synchronize do
|
|
579
|
+
@acks.each_value do |pending_ack|
|
|
580
|
+
pending_ack.queue << :close
|
|
581
|
+
end
|
|
582
|
+
@acks.clear
|
|
583
|
+
end
|
|
584
|
+
|
|
564
585
|
retries = 0
|
|
565
586
|
begin
|
|
566
|
-
connect_internal unless @reconnect_limit
|
|
587
|
+
connect_internal unless @reconnect_limit&.zero?
|
|
567
588
|
rescue
|
|
568
589
|
@socket&.close
|
|
569
590
|
@socket = nil
|
|
570
591
|
retries += 1
|
|
571
592
|
|
|
572
593
|
if @reconnect_limit.nil? || retries < @reconnect_limit
|
|
573
|
-
sleep [@reconnect_backoff
|
|
594
|
+
sleep [@reconnect_backoff**retries, @reconnect_backoff_max].min
|
|
574
595
|
retry
|
|
575
596
|
end
|
|
576
597
|
end
|
|
577
598
|
|
|
578
599
|
unless @socket
|
|
579
600
|
# couldn't reconnect
|
|
580
|
-
@acks_mutex.synchronize do
|
|
581
|
-
@acks.each_value do |pending_ack|
|
|
582
|
-
pending_ack.queue << :close
|
|
583
|
-
end
|
|
584
|
-
@acks.clear
|
|
585
|
-
end
|
|
586
601
|
@connected = false
|
|
587
602
|
@read_queue << [exception, current_time]
|
|
588
603
|
return
|
|
@@ -590,7 +605,7 @@ module MQTT
|
|
|
590
605
|
end
|
|
591
606
|
|
|
592
607
|
begin
|
|
593
|
-
if @on_reconnect&.arity
|
|
608
|
+
if @on_reconnect&.arity&.zero?
|
|
594
609
|
@on_reconnect.call
|
|
595
610
|
else
|
|
596
611
|
@on_reconnect&.call(@connack)
|
|
@@ -611,7 +626,7 @@ module MQTT
|
|
|
611
626
|
|
|
612
627
|
# we just needed to break out of our select to set up a new timeout;
|
|
613
628
|
# we can discard the actual contents
|
|
614
|
-
@wake_up_pipe[0].
|
|
629
|
+
@wake_up_pipe[0].read_nonblock(4096, exception: false) if read_ready&.include?(@wake_up_pipe[0])
|
|
615
630
|
|
|
616
631
|
handle_timeouts
|
|
617
632
|
|
|
@@ -632,7 +647,7 @@ module MQTT
|
|
|
632
647
|
@acks_mutex.synchronize do
|
|
633
648
|
if @acks.empty?
|
|
634
649
|
# just need to wake up the read thread to set up the timeout for this packet
|
|
635
|
-
@wake_up_pipe[1].write(
|
|
650
|
+
@wake_up_pipe[1].write("z")
|
|
636
651
|
end
|
|
637
652
|
@acks[packet.id] = PendingAck.new(packet, queue, timeout_at, 1)
|
|
638
653
|
end
|
|
@@ -691,7 +706,7 @@ module MQTT
|
|
|
691
706
|
return
|
|
692
707
|
end
|
|
693
708
|
# timed out, or simple re-send
|
|
694
|
-
@wake_up_pipe[1].write(
|
|
709
|
+
@wake_up_pipe[1].write("z") if @acks.first.first == packet.id
|
|
695
710
|
pending_ack.timeout_at = current_time + @ack_timeout
|
|
696
711
|
packet.duplicate = true
|
|
697
712
|
send_packet(packet)
|
|
@@ -719,7 +734,7 @@ module MQTT
|
|
|
719
734
|
end
|
|
720
735
|
|
|
721
736
|
def handle_keep_alives
|
|
722
|
-
return unless @keep_alive
|
|
737
|
+
return unless @keep_alive&.positive?
|
|
723
738
|
|
|
724
739
|
current_time_local = current_time
|
|
725
740
|
if current_time_local >= @last_packet_sent_at + @keep_alive && !@keep_alive_sent
|
|
@@ -764,20 +779,20 @@ module MQTT
|
|
|
764
779
|
def parse_uri(uri)
|
|
765
780
|
uri = URI.parse(uri) unless uri.is_a?(URI)
|
|
766
781
|
ssl = case uri.scheme
|
|
767
|
-
when
|
|
782
|
+
when "mqtt"
|
|
768
783
|
false
|
|
769
|
-
when
|
|
784
|
+
when "mqtts"
|
|
770
785
|
true
|
|
771
786
|
else
|
|
772
|
-
raise
|
|
787
|
+
raise "Only the mqtt:// and mqtts:// schemes are supported"
|
|
773
788
|
end
|
|
774
789
|
|
|
775
790
|
{
|
|
776
791
|
host: uri.host,
|
|
777
792
|
port: uri.port || nil,
|
|
778
|
-
username: uri.user ? URI::
|
|
779
|
-
password: uri.password ? URI::
|
|
780
|
-
ssl:
|
|
793
|
+
username: uri.user ? URI::RFC2396_PARSER.unescape(uri.user) : nil,
|
|
794
|
+
password: uri.password ? URI::RFC2396_PARSER.unescape(uri.password) : nil,
|
|
795
|
+
ssl:
|
|
781
796
|
}
|
|
782
797
|
end
|
|
783
798
|
|
|
@@ -786,5 +801,18 @@ module MQTT
|
|
|
786
801
|
@last_packet_id = 1 if @last_packet_id > 0xffff
|
|
787
802
|
@last_packet_id
|
|
788
803
|
end
|
|
804
|
+
|
|
805
|
+
def open_tcp_socket
|
|
806
|
+
return TCPSocket.new @host, @port, connect_timeout: @connect_timeout if RUBY_VERSION.to_f >= 3.0
|
|
807
|
+
|
|
808
|
+
begin
|
|
809
|
+
Timeout.timeout(@connect_timeout) do
|
|
810
|
+
return TCPSocket.new(@host, @port)
|
|
811
|
+
end
|
|
812
|
+
rescue Timeout::Error
|
|
813
|
+
raise (defined?(IO::TimeoutError) ? IO::TimeoutError : Errno::ETIMEDOUT),
|
|
814
|
+
"Connection timed out for \"#{@host}\" port #{@port}"
|
|
815
|
+
end
|
|
816
|
+
end
|
|
789
817
|
end
|
|
790
818
|
end
|