mqtt 0.5.0 → 0.7.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/lib/mqtt/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module MQTT
2
2
  # The version number of the MQTT gem
3
- VERSION = "0.5.0"
3
+ VERSION = '0.7.0'
4
4
  end
data/lib/mqtt.rb CHANGED
@@ -7,13 +7,7 @@ require 'timeout'
7
7
 
8
8
  require 'mqtt/version'
9
9
 
10
- # String encoding monkey patch for Ruby 1.8
11
- unless String.method_defined?(:force_encoding)
12
- require 'mqtt/patches/string_encoding.rb'
13
- end
14
-
15
10
  module MQTT
16
-
17
11
  # Default port number for unencrypted connections
18
12
  DEFAULT_PORT = 1883
19
13
 
@@ -41,7 +35,6 @@ module MQTT
41
35
 
42
36
  # MQTT-SN
43
37
  module SN
44
-
45
38
  # Default port number for unencrypted connections
46
39
  DEFAULT_PORT = 1883
47
40
 
@@ -50,6 +43,6 @@ module MQTT
50
43
  class ProtocolException < MQTT::Exception
51
44
  end
52
45
 
53
- autoload :Packet, 'mqtt/sn/packet'
46
+ autoload :Packet, 'mqtt/sn/packet'
54
47
  end
55
48
  end
@@ -23,6 +23,17 @@ describe MQTT::Client do
23
23
  end
24
24
  end
25
25
 
26
+ if Process.const_defined? :CLOCK_MONOTONIC
27
+ def now
28
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
29
+ end
30
+ else
31
+ # Support older Ruby
32
+ def now
33
+ Time.now.to_f
34
+ end
35
+ end
36
+
26
37
  describe "initializing a client" do
27
38
  it "with no arguments, it should use the defaults" do
28
39
  client = MQTT::Client.new
@@ -221,10 +232,46 @@ describe MQTT::Client do
221
232
  describe "setting an encrypted client private key, w/an incorrect passphrase" do
222
233
  let(:key_pass) { 'ttqm' }
223
234
 
224
- it "should raise an OpenSSL::PKey::RSAError exception" do
235
+ it "should raise an exception" do
225
236
  expect(client.ssl_context.key).to be_nil
226
237
  expect { client.key_file = [fixture_path('client.pass.key'), key_pass] }.to(
227
- raise_error(OpenSSL::PKey::RSAError, /Neither PUB key nor PRIV key/))
238
+ raise_error(/Could not parse PKey/))
239
+ end
240
+ end
241
+
242
+ describe "setting a client private EC key file path" do
243
+ it "should add a certificate to the SSL context" do
244
+ expect(client.ssl_context.key).to be_nil
245
+ client.key_file = fixture_path('ec.key')
246
+ expect(client.ssl_context.key).to be_a(OpenSSL::PKey::EC)
247
+ end
248
+ end
249
+
250
+ describe "setting a client private EC key directly" do
251
+ it "should add a certificate to the SSL context" do
252
+ expect(client.ssl_context.key).to be_nil
253
+ client.key = File.read(fixture_path('ec.key'))
254
+ expect(client.ssl_context.key).to be_a(OpenSSL::PKey::EC)
255
+ end
256
+ end
257
+
258
+ describe "setting an encrypted client private EC key, w/the correct passphrase" do
259
+ let(:key_pass) { 'mqtt' }
260
+
261
+ it "should add the decrypted certificate to the SSL context" do
262
+ expect(client.ssl_context.key).to be_nil
263
+ client.key_file = [fixture_path('ec.pass.key'), key_pass]
264
+ expect(client.ssl_context.key).to be_a(OpenSSL::PKey::EC)
265
+ end
266
+ end
267
+
268
+ describe "setting an encrypted client private EC key, w/an incorrect passphrase" do
269
+ let(:key_pass) { 'ttqm' }
270
+
271
+ it "should raise an exception" do
272
+ expect(client.ssl_context.key).to be_nil
273
+ expect { client.key_file = [fixture_path('ec.pass.key'), key_pass] }.to(
274
+ raise_error(/Could not parse PKey/))
228
275
  end
229
276
  end
230
277
 
@@ -395,6 +442,7 @@ describe MQTT::Client do
395
442
  it "should use ssl if it enabled using the :ssl => true parameter" do
396
443
  expect(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(ssl_socket)
397
444
  expect(ssl_socket).to receive(:connect)
445
+ expect(ssl_socket).to receive(:post_connection_check).with('mqtt.example.com')
398
446
 
399
447
  client = MQTT::Client.new('mqtt.example.com', :ssl => true)
400
448
  allow(client).to receive(:receive_connack)
@@ -404,6 +452,7 @@ describe MQTT::Client do
404
452
  it "should use ssl if it enabled using the mqtts:// scheme" do
405
453
  expect(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(ssl_socket)
406
454
  expect(ssl_socket).to receive(:connect)
455
+ expect(ssl_socket).to receive(:post_connection_check).with('mqtt.example.com')
407
456
 
408
457
  client = MQTT::Client.new('mqtts://mqtt.example.com')
409
458
  allow(client).to receive(:receive_connack)
@@ -413,6 +462,7 @@ describe MQTT::Client do
413
462
  it "should use set the SSL version, if the :ssl parameter is a symbol" do
414
463
  expect(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(ssl_socket)
415
464
  expect(ssl_socket).to receive(:connect)
465
+ expect(ssl_socket).to receive(:post_connection_check).with('mqtt.example.com')
416
466
 
417
467
  client = MQTT::Client.new('mqtt.example.com', :ssl => :TLSv1)
418
468
  expect(client.ssl_context).to receive('ssl_version=').with(:TLSv1)
@@ -423,11 +473,21 @@ describe MQTT::Client do
423
473
  it "should use set hostname on the SSL socket for SNI" do
424
474
  expect(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(ssl_socket)
425
475
  expect(ssl_socket).to receive(:hostname=).with('mqtt.example.com')
476
+ expect(ssl_socket).to receive(:post_connection_check).with('mqtt.example.com')
426
477
 
427
478
  client = MQTT::Client.new('mqtts://mqtt.example.com')
428
479
  allow(client).to receive(:receive_connack)
429
480
  client.connect
430
481
  end
482
+
483
+ it "should skip host verification" do
484
+ expect(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(ssl_socket)
485
+ expect(ssl_socket).to receive(:connect)
486
+
487
+ client = MQTT::Client.new('mqtt.example.com', :ssl => true, :verify_host => false)
488
+ allow(client).to receive(:receive_connack)
489
+ client.connect
490
+ end
431
491
  end
432
492
 
433
493
  context "with a last will and testament set" do
@@ -571,10 +631,61 @@ describe MQTT::Client do
571
631
  end
572
632
 
573
633
  describe "when calling the 'publish' method" do
634
+ class ClientWithPubackInjection < MQTT::Client
635
+ def initialize
636
+ super(:host => 'localhost')
637
+ @injected_pubacks = {}
638
+ end
639
+
640
+ def inject_puback(packet)
641
+ @injected_pubacks[packet.id] = packet
642
+ end
643
+
644
+ def wait_for_puback(id)
645
+ packet = @injected_pubacks.fetch(id) {
646
+ return super
647
+ }
648
+ Queue.new << packet
649
+ end
650
+ end
651
+
652
+ let(:client) { ClientWithPubackInjection.new }
653
+
574
654
  before(:each) do
575
655
  client.instance_variable_set('@socket', socket)
576
656
  end
577
657
 
658
+ it "should respect connect_timeout" do
659
+ client = MQTT::Client.new(:host => '198.51.100.1', :connect_timeout => 0.1)
660
+ expect {
661
+ client.connect
662
+ }.to raise_error(
663
+ if defined? IO::TimeoutError
664
+ IO::TimeoutError
665
+ else
666
+ Errno::ETIMEDOUT
667
+ end
668
+ )
669
+ end
670
+
671
+ it "should respect timeouts" do
672
+ require "socket"
673
+ rd, wr = UNIXSocket.pair
674
+ client = MQTT::Client.new(:host => 'localhost', :ack_timeout => 1.0)
675
+ client.instance_variable_set('@socket', rd)
676
+ t = Thread.new {
677
+ Thread.current[:parent] = Thread.main
678
+ loop do
679
+ client.send :receive_packet
680
+ end
681
+ }
682
+ start = now
683
+ expect(client.publish('topic','payload', false, 1)).to eq(-1)
684
+ elapsed = now - start
685
+ t.kill
686
+ expect(elapsed).to be_within(0.1).of(1.0)
687
+ end
688
+
578
689
  it "should write a valid PUBLISH packet to the socket without the retain flag" do
579
690
  client.publish('topic','payload', false, 0)
580
691
  expect(socket.string).to eq("\x30\x0e\x00\x05topicpayload")
@@ -591,6 +702,19 @@ describe MQTT::Client do
591
702
  expect(socket.string).to eq("\x32\x10\x00\x05topic\x00\x01payload")
592
703
  end
593
704
 
705
+ it "should wrap the packet id after 65535" do
706
+ 0xffff.times do |n|
707
+ inject_puback(n + 1)
708
+ client.publish('topic','payload', false, 1)
709
+ end
710
+ expect(client.instance_variable_get(:@last_packet_id)).to eq(0xffff)
711
+
712
+ socket.string = ""
713
+ inject_puback(1)
714
+ client.publish('topic','payload', false, 1)
715
+ expect(socket.string).to eq("\x32\x10\x00\x05topic\x00\x01payload")
716
+ end
717
+
594
718
  it "should write a valid PUBLISH packet to the socket with the QoS set to 2" do
595
719
  inject_puback(1)
596
720
  client.publish('topic','payload', false, 2)
@@ -634,6 +758,20 @@ describe MQTT::Client do
634
758
  expect(client).to receive(:send_packet) { |packet| expect(packet.id).to eq(2) }
635
759
  client.publish "topic", "message", false, 1
636
760
  end
761
+
762
+ it "does not crash when receiving a PUBACK for a packet it never sent" do
763
+ expect { client.send(:handle_packet, MQTT::Packet::Puback.new(:id => 666)) }.to_not raise_error
764
+ end
765
+
766
+ it "does not crash with QoS 1 when the broker sends the PUBACK instantly" do
767
+ allow(client).to receive(:send_packet).and_wrap_original do |send_packet, packet, *args, &block|
768
+ send_packet.call(packet, *args, &block).tap do
769
+ client.send(:handle_packet, MQTT::Packet::Puback.new(:id => packet.id))
770
+ end
771
+ end
772
+
773
+ expect { client.publish("topic", "message", false, 1) }.to_not raise_error
774
+ end
637
775
  end
638
776
 
639
777
  describe "when calling the 'subscribe' method" do
@@ -690,6 +828,15 @@ describe MQTT::Client do
690
828
  end
691
829
  end
692
830
 
831
+ describe "when calling the 'clear_queue' method" do
832
+ it "should clear the waiting incoming messages" do
833
+ inject_packet(:topic => 'topic0', :payload => 'payload0', :qos => 0)
834
+ expect(client.queue_length).to eq(1)
835
+ client.clear_queue
836
+ expect(client.queue_length).to eq(0)
837
+ end
838
+ end
839
+
693
840
  describe "when calling the 'get' method" do
694
841
  before(:each) do
695
842
  client.instance_variable_set('@socket', socket)
@@ -852,15 +999,29 @@ describe MQTT::Client do
852
999
  expect(@read_queue.size).to eq(0)
853
1000
  end
854
1001
 
855
- it "should close the socket if there is an exception" do
1002
+ it "should close the socket if there is an MQTT exception" do
856
1003
  expect(socket).to receive(:close).once
857
1004
  allow(MQTT::Packet).to receive(:read).and_raise(MQTT::Exception)
858
1005
  client.send(:receive_packet)
859
1006
  end
860
1007
 
1008
+ it "should close the socket if there is a system call error" do
1009
+ expect(socket).to receive(:close).once
1010
+ allow(MQTT::Packet).to receive(:read).and_raise(Errno::ECONNRESET)
1011
+ client.send(:receive_packet)
1012
+ end
1013
+
861
1014
  it "should pass exceptions up to parent thread" do
862
- expect(@parent_thread).to receive(:raise).once
863
- allow(MQTT::Packet).to receive(:read).and_raise(MQTT::Exception)
1015
+ e = MQTT::Exception.new
1016
+ expect(@parent_thread).to receive(:raise).with(e).once
1017
+ allow(MQTT::Packet).to receive(:read).and_raise(e)
1018
+ client.send(:receive_packet)
1019
+ end
1020
+
1021
+ it "should pass a system call error up to parent thread" do
1022
+ e = Errno::ECONNRESET.new
1023
+ expect(@parent_thread).to receive(:raise).with(e).once
1024
+ allow(MQTT::Packet).to receive(:read).and_raise(e)
864
1025
  client.send(:receive_packet)
865
1026
  end
866
1027
 
@@ -868,7 +1029,7 @@ describe MQTT::Client do
868
1029
  allow(MQTT::Packet).to receive(:read).and_return MQTT::Packet::Pingresp.new
869
1030
  client.instance_variable_set '@last_ping_response', Time.at(0)
870
1031
  client.send :receive_packet
871
- expect(client.last_ping_response).to be_within(1).of Time.now
1032
+ expect(client.last_ping_response).to be_within(1).of now
872
1033
  end
873
1034
  end
874
1035
 
@@ -878,20 +1039,20 @@ describe MQTT::Client do
878
1039
  end
879
1040
 
880
1041
  it "should send a ping packet if one is due" do
881
- client.instance_variable_set('@last_ping_request', Time.at(0))
1042
+ client.instance_variable_set('@last_ping_request', 0.0)
882
1043
  client.send('keep_alive!')
883
1044
  expect(socket.string).to eq("\xC0\x00")
884
1045
  end
885
1046
 
886
1047
  it "should update the time a ping was last sent" do
887
- client.instance_variable_set('@last_ping_request', Time.at(0))
1048
+ client.instance_variable_set('@last_ping_request', 0.0)
888
1049
  client.send('keep_alive!')
889
- expect(client.instance_variable_get('@last_ping_request')).not_to eq(0)
1050
+ expect(client.instance_variable_get('@last_ping_request')).to be_within(0.01).of(now)
890
1051
  end
891
1052
 
892
1053
  it "should raise an exception if no ping response has been received" do
893
- client.instance_variable_set('@last_ping_request', Time.now)
894
- client.instance_variable_set('@last_ping_response', Time.at(0))
1054
+ client.instance_variable_set('@last_ping_request', now)
1055
+ client.instance_variable_set('@last_ping_response', 0.0)
895
1056
  expect {
896
1057
  client.send('keep_alive!')
897
1058
  }.to raise_error(
@@ -951,7 +1112,7 @@ describe MQTT::Client do
951
1112
 
952
1113
  def inject_puback(packet_id)
953
1114
  packet = MQTT::Packet::Puback.new(:id => packet_id)
954
- client.instance_variable_get('@pubacks')[packet_id] = packet
1115
+ client.inject_puback packet
955
1116
  end
956
1117
 
957
1118
  end
@@ -49,6 +49,14 @@ describe MQTT::Packet do
49
49
  expect(data.encoding.to_s).to eq("ASCII-8BIT")
50
50
  end
51
51
 
52
+ it "should raise an error if too big argument for encode_short" do
53
+ expect {
54
+ data = packet.send(:encode_short, 0x10000)
55
+ }.to raise_error(
56
+ 'Value too big for short'
57
+ )
58
+ end
59
+
52
60
  it "should provide a add_string method to get a string preceeded by its length" do
53
61
  data = packet.send(:encode_string, 'quack')
54
62
  expect(data).to eq("\x00\x05quack")
@@ -374,8 +382,7 @@ describe MQTT::Packet::Publish do
374
382
  expect(packet.topic.bytesize).to eq(8)
375
383
  end
376
384
 
377
- it "should have the correct topic string length", :unless => RUBY_VERSION =~ /^1\.8/ do
378
- # Ruby 1.8 doesn't support UTF-8 properly
385
+ it "should have the correct topic string length" do
379
386
  expect(packet.topic.length).to eq(6)
380
387
  end
381
388
 
@@ -383,8 +390,7 @@ describe MQTT::Packet::Publish do
383
390
  expect(packet.payload.bytesize).to eq(12)
384
391
  end
385
392
 
386
- it "should have the correct payload string length", :unless => RUBY_VERSION =~ /^1\.8/ do
387
- # Ruby 1.8 doesn't support UTF-8 properly
393
+ it "should have the correct payload string length" do
388
394
  expect(packet.payload.length).to eq(10)
389
395
  end
390
396
 
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mqtt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicholas J Humfrey
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-16 00:00:00.000000000 Z
11
+ date: 2025-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logger
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +58,14 @@ dependencies:
44
58
  requirements:
45
59
  - - ">="
46
60
  - !ruby/object:Gem::Version
47
- version: 0.8.7
61
+ version: 0.9.11
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - ">="
53
67
  - !ruby/object:Gem::Version
54
- version: 0.8.7
68
+ version: 0.9.11
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,20 @@ dependencies:
80
94
  - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: 0.9.2
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.45'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.45'
83
111
  description: Pure Ruby gem that implements the MQTT protocol, a lightweight protocol
84
112
  for publish/subscribe messaging.
85
113
  email: njh@aelius.com
@@ -92,8 +120,8 @@ files:
92
120
  - README.md
93
121
  - lib/mqtt.rb
94
122
  - lib/mqtt/client.rb
123
+ - lib/mqtt/openssl_fix.rb
95
124
  - lib/mqtt/packet.rb
96
- - lib/mqtt/patches/string_encoding.rb
97
125
  - lib/mqtt/proxy.rb
98
126
  - lib/mqtt/sn/packet.rb
99
127
  - lib/mqtt/version.rb
@@ -103,11 +131,11 @@ files:
103
131
  - spec/mqtt_sn_packet_spec.rb
104
132
  - spec/mqtt_version_spec.rb
105
133
  - spec/zz_client_integration_spec.rb
106
- homepage: http://github.com/njh/ruby-mqtt
134
+ homepage: https://github.com/njh/ruby-mqtt
107
135
  licenses:
108
136
  - MIT
109
137
  metadata: {}
110
- post_install_message:
138
+ post_install_message:
111
139
  rdoc_options: []
112
140
  require_paths:
113
141
  - lib
@@ -122,9 +150,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
150
  - !ruby/object:Gem::Version
123
151
  version: '0'
124
152
  requirements: []
125
- rubyforge_project: mqtt
126
- rubygems_version: 2.4.5
127
- signing_key:
153
+ rubygems_version: 3.5.3
154
+ signing_key:
128
155
  specification_version: 4
129
156
  summary: Implementation of the MQTT protocol
130
157
  test_files:
@@ -1,34 +0,0 @@
1
- # Monkey patch to add stubbed string encoding functions to Ruby 1.8
2
-
3
- class String
4
- def force_encoding(encoding)
5
- @encoding = encoding
6
- self
7
- end
8
-
9
- def encoding
10
- @encoding ||= Encoding::ASCII_8BIT
11
- end
12
-
13
- def encode(encoding)
14
- new = self.dup
15
- new.force_encoding(encoding)
16
- end
17
- end
18
-
19
- class Encoding
20
- def initialize(name)
21
- @name = name
22
- end
23
-
24
- def to_s
25
- @name
26
- end
27
-
28
- def name
29
- @name
30
- end
31
-
32
- UTF_8 = Encoding.new("UTF-8")
33
- ASCII_8BIT = Encoding.new("ASCII-8BIT")
34
- end