mqtt 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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.4.0"
3
+ VERSION = '0.6.0'
4
4
  end
data/lib/mqtt.rb CHANGED
@@ -13,7 +13,6 @@ unless String.method_defined?(:force_encoding)
13
13
  end
14
14
 
15
15
  module MQTT
16
-
17
16
  # Default port number for unencrypted connections
18
17
  DEFAULT_PORT = 1883
19
18
 
@@ -41,7 +40,6 @@ module MQTT
41
40
 
42
41
  # MQTT-SN
43
42
  module SN
44
-
45
43
  # Default port number for unencrypted connections
46
44
  DEFAULT_PORT = 1883
47
45
 
@@ -50,6 +48,6 @@ module MQTT
50
48
  class ProtocolException < MQTT::Exception
51
49
  end
52
50
 
53
- autoload :Packet, 'mqtt/sn/packet'
51
+ autoload :Packet, 'mqtt/sn/packet'
54
52
  end
55
53
  end
@@ -23,12 +23,23 @@ 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
29
40
  expect(client.host).to eq(nil)
30
41
  expect(client.port).to eq(1883)
31
- expect(client.version).to eq('3.1.0')
42
+ expect(client.version).to eq('3.1.1')
32
43
  expect(client.keep_alive).to eq(15)
33
44
  end
34
45
 
@@ -117,6 +128,22 @@ describe MQTT::Client do
117
128
  expect(client.password).to eq('bpass')
118
129
  end
119
130
 
131
+ it "with a URI containing an escaped username and password" do
132
+ client = MQTT::Client.new(URI.parse('mqtt://foo%20bar:%40123%2B%25@mqtt.example.com'))
133
+ expect(client.host).to eq('mqtt.example.com')
134
+ expect(client.port).to eq(1883)
135
+ expect(client.username).to eq('foo bar')
136
+ expect(client.password).to eq('@123+%')
137
+ end
138
+
139
+ it "with a URI containing a double escaped username and password" do
140
+ client = MQTT::Client.new(URI.parse('mqtt://foo%2520bar:123%2525@mqtt.example.com'))
141
+ expect(client.host).to eq('mqtt.example.com')
142
+ expect(client.port).to eq(1883)
143
+ expect(client.username).to eq('foo%20bar')
144
+ expect(client.password).to eq('123%25')
145
+ end
146
+
120
147
  it "with a URI as a string" do
121
148
  client = MQTT::Client.new('mqtt://mqtt.example.com')
122
149
  expect(client.host).to eq('mqtt.example.com')
@@ -175,7 +202,7 @@ describe MQTT::Client do
175
202
  expect(client.ssl_context.cert).to be_a(OpenSSL::X509::Certificate)
176
203
  end
177
204
  end
178
-
205
+
179
206
  describe "setting a client private key file path" do
180
207
  it "should add a certificate to the SSL context" do
181
208
  expect(client.ssl_context.key).to be_nil
@@ -323,9 +350,9 @@ describe MQTT::Client do
323
350
  client.password = 'password'
324
351
  client.connect('myclient')
325
352
  expect(socket.string).to eq(
326
- "\x10\x2A"+
327
- "\x00\x06MQIsdp"+
328
- "\x03\xC2\x00\x0f"+
353
+ "\x10\x28"+
354
+ "\x00\x04MQTT"+
355
+ "\x04\xC2\x00\x0f"+
329
356
  "\x00\x08myclient"+
330
357
  "\x00\x08username"+
331
358
  "\x00\x08password"
@@ -403,6 +430,15 @@ describe MQTT::Client do
403
430
  allow(client).to receive(:receive_connack)
404
431
  client.connect
405
432
  end
433
+
434
+ it "should use set hostname on the SSL socket for SNI" do
435
+ expect(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(ssl_socket)
436
+ expect(ssl_socket).to receive(:hostname=).with('mqtt.example.com')
437
+
438
+ client = MQTT::Client.new('mqtts://mqtt.example.com')
439
+ allow(client).to receive(:receive_connack)
440
+ client.connect
441
+ end
406
442
  end
407
443
 
408
444
  context "with a last will and testament set" do
@@ -429,9 +465,9 @@ describe MQTT::Client do
429
465
  it "should include the will in the CONNECT message" do
430
466
  client.connect('myclient')
431
467
  expect(socket.string).to eq(
432
- "\x10\x24"+
433
- "\x00\x06MQIsdp"+
434
- "\x03\x0e\x00\x0f"+
468
+ "\x10\x22"+
469
+ "\x00\x04MQTT"+
470
+ "\x04\x0e\x00\x0f"+
435
471
  "\x00\x08myclient"+
436
472
  "\x00\x05topic\x00\x05hello"
437
473
  )
@@ -473,6 +509,7 @@ describe MQTT::Client do
473
509
  socket.write("\x20\x02\x00\x00")
474
510
  socket.rewind
475
511
  expect { client.send(:receive_connack) }.not_to raise_error
512
+ expect(socket).not_to be_closed
476
513
  end
477
514
 
478
515
  it "should raise an exception if the packet type isn't CONNACK" do
@@ -504,6 +541,13 @@ describe MQTT::Client do
504
541
  socket.rewind
505
542
  expect { client.send(:receive_connack) }.to raise_error(MQTT::ProtocolException, /connection refused/i)
506
543
  end
544
+
545
+ it "should close the socket for an unsuccessful CONNACK packet" do
546
+ socket.write("\x20\x02\x00\x05")
547
+ socket.rewind
548
+ expect { client.send(:receive_connack) }.to raise_error(MQTT::ProtocolException, /not authorised/i)
549
+ expect(socket).to be_closed
550
+ end
507
551
  end
508
552
 
509
553
  describe "when calling the 'disconnect' method" do
@@ -538,10 +582,48 @@ describe MQTT::Client do
538
582
  end
539
583
 
540
584
  describe "when calling the 'publish' method" do
585
+ class ClientWithPubackInjection < MQTT::Client
586
+ def initialize
587
+ super(:host => 'localhost')
588
+ @injected_pubacks = {}
589
+ end
590
+
591
+ def inject_puback(packet)
592
+ @injected_pubacks[packet.id] = packet
593
+ end
594
+
595
+ def wait_for_puback(id, queue)
596
+ packet = @injected_pubacks.fetch(id) {
597
+ return super
598
+ }
599
+ queue << packet
600
+ end
601
+ end
602
+
603
+ let(:client) { ClientWithPubackInjection.new }
604
+
541
605
  before(:each) do
542
606
  client.instance_variable_set('@socket', socket)
543
607
  end
544
608
 
609
+ it "should respect timeouts" do
610
+ require "socket"
611
+ rd, wr = UNIXSocket.pair
612
+ client = MQTT::Client.new(:host => 'localhost', :ack_timeout => 1.0)
613
+ client.instance_variable_set('@socket', rd)
614
+ t = Thread.new {
615
+ Thread.current[:parent] = Thread.main
616
+ loop do
617
+ client.send :receive_packet
618
+ end
619
+ }
620
+ start = now
621
+ expect(client.publish('topic','payload', false, 1)).to eq(-1)
622
+ elapsed = now - start
623
+ t.kill
624
+ expect(elapsed).to be_within(0.1).of(1.0)
625
+ end
626
+
545
627
  it "should write a valid PUBLISH packet to the socket without the retain flag" do
546
628
  client.publish('topic','payload', false, 0)
547
629
  expect(socket.string).to eq("\x30\x0e\x00\x05topicpayload")
@@ -558,6 +640,19 @@ describe MQTT::Client do
558
640
  expect(socket.string).to eq("\x32\x10\x00\x05topic\x00\x01payload")
559
641
  end
560
642
 
643
+ it "should wrap the packet id after 65535" do
644
+ 0xffff.times do |n|
645
+ inject_puback(n + 1)
646
+ client.publish('topic','payload', false, 1)
647
+ end
648
+ expect(client.instance_variable_get(:@last_packet_id)).to eq(0xffff)
649
+
650
+ socket.string = ""
651
+ inject_puback(1)
652
+ client.publish('topic','payload', false, 1)
653
+ expect(socket.string).to eq("\x32\x10\x00\x05topic\x00\x01payload")
654
+ end
655
+
561
656
  it "should write a valid PUBLISH packet to the socket with the QoS set to 2" do
562
657
  inject_puback(1)
563
658
  client.publish('topic','payload', false, 2)
@@ -595,7 +690,7 @@ describe MQTT::Client do
595
690
  it "correctly assigns consecutive ids to packets with QoS 1" do
596
691
  inject_puback(1)
597
692
  inject_puback(2)
598
-
693
+
599
694
  expect(client).to receive(:send_packet) { |packet| expect(packet.id).to eq(1) }
600
695
  client.publish "topic", "message", false, 1
601
696
  expect(client).to receive(:send_packet) { |packet| expect(packet.id).to eq(2) }
@@ -657,6 +752,15 @@ describe MQTT::Client do
657
752
  end
658
753
  end
659
754
 
755
+ describe "when calling the 'clear_queue' method" do
756
+ it "should clear the waiting incoming messages" do
757
+ inject_packet(:topic => 'topic0', :payload => 'payload0', :qos => 0)
758
+ expect(client.queue_length).to eq(1)
759
+ client.clear_queue
760
+ expect(client.queue_length).to eq(0)
761
+ end
762
+ end
763
+
660
764
  describe "when calling the 'get' method" do
661
765
  before(:each) do
662
766
  client.instance_variable_set('@socket', socket)
@@ -677,6 +781,15 @@ describe MQTT::Client do
677
781
  expect(client.queue_empty?).to be_truthy
678
782
  end
679
783
 
784
+ it "should successfully receive a valid PUBLISH packet, but not return it, if omit_retained is set" do
785
+ inject_packet(:topic => 'topic1', :payload => 'payload1', :qos => 1, :retain => 1)
786
+ inject_packet(:topic => 'topic1', :payload => 'payload2', :qos => 1)
787
+ topic,payload = client.get(nil, :omit_retained => true)
788
+ expect(topic).to eq('topic1')
789
+ expect(payload).to eq('payload2')
790
+ expect(client.queue_empty?).to be_truthy
791
+ end
792
+
680
793
  it "acks calling #get_packet and qos=1" do
681
794
  inject_packet(:topic => 'topic1', :payload => 'payload1', :qos => 1)
682
795
  expect(client).to receive(:send_packet).with(an_instance_of(MQTT::Packet::Puback))
@@ -712,6 +825,18 @@ describe MQTT::Client do
712
825
  break if payloads.size > 1
713
826
  end
714
827
  end
828
+
829
+ it "should ignore a PUBLISH message when it is marked as retained and omit_retained is set" do
830
+ inject_packet(:topic => 'topic0', :payload => 'payload0', :retain => 1)
831
+ inject_packet(:topic => 'topic1', :payload => 'payload1')
832
+ payloads = []
833
+ client.get(nil, :omit_retained => true) do |topic,payload|
834
+ payloads << payload
835
+ break if payloads.size > 0
836
+ end
837
+ expect(payloads.size).to eq(1)
838
+ expect(payloads).to eq(['payload1'])
839
+ end
715
840
  end
716
841
  end
717
842
 
@@ -814,7 +939,7 @@ describe MQTT::Client do
814
939
  allow(MQTT::Packet).to receive(:read).and_return MQTT::Packet::Pingresp.new
815
940
  client.instance_variable_set '@last_ping_response', Time.at(0)
816
941
  client.send :receive_packet
817
- expect(client.last_ping_response).to be_within(1).of Time.now
942
+ expect(client.last_ping_response).to be_within(1).of now
818
943
  end
819
944
  end
820
945
 
@@ -824,20 +949,20 @@ describe MQTT::Client do
824
949
  end
825
950
 
826
951
  it "should send a ping packet if one is due" do
827
- client.instance_variable_set('@last_ping_request', Time.at(0))
952
+ client.instance_variable_set('@last_ping_request', 0.0)
828
953
  client.send('keep_alive!')
829
954
  expect(socket.string).to eq("\xC0\x00")
830
955
  end
831
956
 
832
957
  it "should update the time a ping was last sent" do
833
- client.instance_variable_set('@last_ping_request', Time.at(0))
958
+ client.instance_variable_set('@last_ping_request', 0.0)
834
959
  client.send('keep_alive!')
835
- expect(client.instance_variable_get('@last_ping_request')).not_to eq(0)
960
+ expect(client.instance_variable_get('@last_ping_request')).to be_within(0.01).of(now)
836
961
  end
837
962
 
838
963
  it "should raise an exception if no ping response has been received" do
839
- client.instance_variable_set('@last_ping_request', Time.now)
840
- client.instance_variable_set('@last_ping_response', Time.at(0))
964
+ client.instance_variable_set('@last_ping_request', now)
965
+ client.instance_variable_set('@last_ping_response', 0.0)
841
966
  expect {
842
967
  client.send('keep_alive!')
843
968
  }.to raise_error(
@@ -845,6 +970,13 @@ describe MQTT::Client do
845
970
  /No Ping Response received for \d+ seconds/
846
971
  )
847
972
  end
973
+
974
+ it "should not raise an exception if no ping response received and client is disconnected" do
975
+ client.instance_variable_set('@last_ping_request', Time.now)
976
+ client.instance_variable_set('@last_ping_response', Time.at(0))
977
+ client.disconnect(false)
978
+ client.send('keep_alive!')
979
+ end
848
980
  end
849
981
 
850
982
  describe "generating a client identifier" do
@@ -890,7 +1022,7 @@ describe MQTT::Client do
890
1022
 
891
1023
  def inject_puback(packet_id)
892
1024
  packet = MQTT::Packet::Puback.new(:id => packet_id)
893
- client.instance_variable_get('@pubacks')[packet_id] = packet
1025
+ client.inject_puback packet
894
1026
  end
895
1027
 
896
1028
  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")
@@ -24,9 +24,13 @@ describe "a client talking to a server" do
24
24
  end
25
25
 
26
26
  context "connecting and publishing a packet" do
27
- def connect_and_publish
27
+ def connect_and_publish(options = {})
28
28
  @client.connect
29
- @client.publish('test', 'foobar')
29
+
30
+ retain = options.fetch(:retain) { false }
31
+ qos = options.fetch(:qos) { 0 }
32
+
33
+ @client.publish('test', 'foobar', retain, qos)
30
34
  @client.disconnect
31
35
  @server.thread.join(1)
32
36
  end
@@ -50,6 +54,13 @@ describe "a client talking to a server" do
50
54
  connect_and_publish
51
55
  expect(@error_log.string).to be_empty
52
56
  end
57
+
58
+ context "with qos > 0" do
59
+ it "the server should have received a packet without timeout" do
60
+ connect_and_publish(:qos => 1)
61
+ expect(@server.last_publish).not_to be_nil
62
+ end
63
+ end
53
64
  end
54
65
 
55
66
  context "connecting, subscribing to a topic and getting a message" do
metadata CHANGED
@@ -1,85 +1,99 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mqtt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.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: 2016-06-27 00:00:00.000000000 Z
11
+ date: 2023-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ! '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 1.11.2
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ! '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.11.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.9.6
33
+ version: 10.2.2
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 0.9.6
40
+ version: 10.2.2
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: yard
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 0.8.0
47
+ version: 0.9.11
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 0.8.0
54
+ version: 0.9.11
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 3.0.0
61
+ version: 3.5.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ~>
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 3.0.0
68
+ version: 3.5.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: simplecov
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ~>
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: 0.9.2
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ~>
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.9.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.45'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.45'
83
97
  description: Pure Ruby gem that implements the MQTT protocol, a lightweight protocol
84
98
  for publish/subscribe messaging.
85
99
  email: njh@aelius.com
@@ -92,6 +106,7 @@ files:
92
106
  - README.md
93
107
  - lib/mqtt.rb
94
108
  - lib/mqtt/client.rb
109
+ - lib/mqtt/openssl_fix.rb
95
110
  - lib/mqtt/packet.rb
96
111
  - lib/mqtt/patches/string_encoding.rb
97
112
  - lib/mqtt/proxy.rb
@@ -103,28 +118,27 @@ files:
103
118
  - spec/mqtt_sn_packet_spec.rb
104
119
  - spec/mqtt_version_spec.rb
105
120
  - spec/zz_client_integration_spec.rb
106
- homepage: http://github.com/njh/ruby-mqtt
121
+ homepage: https://github.com/njh/ruby-mqtt
107
122
  licenses:
108
123
  - MIT
109
124
  metadata: {}
110
- post_install_message:
125
+ post_install_message:
111
126
  rdoc_options: []
112
127
  require_paths:
113
128
  - lib
114
129
  required_ruby_version: !ruby/object:Gem::Requirement
115
130
  requirements:
116
- - - ! '>='
131
+ - - ">="
117
132
  - !ruby/object:Gem::Version
118
133
  version: '0'
119
134
  required_rubygems_version: !ruby/object:Gem::Requirement
120
135
  requirements:
121
- - - ! '>='
136
+ - - ">="
122
137
  - !ruby/object:Gem::Version
123
138
  version: '0'
124
139
  requirements: []
125
- rubyforge_project: mqtt
126
- rubygems_version: 2.5.2
127
- signing_key:
140
+ rubygems_version: 3.3.7
141
+ signing_key:
128
142
  specification_version: 4
129
143
  summary: Implementation of the MQTT protocol
130
144
  test_files:
@@ -134,4 +148,3 @@ test_files:
134
148
  - spec/mqtt_sn_packet_spec.rb
135
149
  - spec/mqtt_version_spec.rb
136
150
  - spec/zz_client_integration_spec.rb
137
- has_rdoc: