paho-mqtt 1.0.7 → 1.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -10
- data/lib/paho-mqtt.rb +44 -62
- data/lib/paho_mqtt/client.rb +51 -43
- data/lib/paho_mqtt/connection_helper.rb +15 -15
- data/lib/paho_mqtt/exception.rb +43 -0
- data/lib/paho_mqtt/handler.rb +21 -33
- data/lib/paho_mqtt/packet/base.rb +27 -19
- data/lib/paho_mqtt/packet/connack.rb +9 -7
- data/lib/paho_mqtt/packet/connect.rb +24 -19
- data/lib/paho_mqtt/packet/disconnect.rb +2 -1
- data/lib/paho_mqtt/packet/pingresp.rb +2 -1
- data/lib/paho_mqtt/packet/puback.rb +2 -1
- data/lib/paho_mqtt/packet/pubcomp.rb +2 -1
- data/lib/paho_mqtt/packet/publish.rb +15 -11
- data/lib/paho_mqtt/packet/pubrec.rb +2 -1
- data/lib/paho_mqtt/packet/pubrel.rb +2 -1
- data/lib/paho_mqtt/packet/suback.rb +6 -4
- data/lib/paho_mqtt/packet/subscribe.rb +10 -7
- data/lib/paho_mqtt/packet/unsuback.rb +2 -1
- data/lib/paho_mqtt/packet/unsubscribe.rb +7 -5
- data/lib/paho_mqtt/publisher.rb +73 -62
- data/lib/paho_mqtt/sender.rb +18 -22
- data/lib/paho_mqtt/subscriber.rb +52 -41
- data/lib/paho_mqtt/version.rb +1 -1
- metadata +3 -2
@@ -0,0 +1,43 @@
|
|
1
|
+
# Copyright (c) 2016-2018 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
|
+
|
16
|
+
module PahoMqtt
|
17
|
+
class Exception < ::StandardError
|
18
|
+
def initialize(msg="")
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class ProtocolViolation < Exception
|
24
|
+
end
|
25
|
+
|
26
|
+
class WritingException < Exception
|
27
|
+
end
|
28
|
+
|
29
|
+
class ReadingException < Exception
|
30
|
+
end
|
31
|
+
|
32
|
+
class PacketException < Exception
|
33
|
+
end
|
34
|
+
|
35
|
+
class PacketFormatException < Exception
|
36
|
+
end
|
37
|
+
|
38
|
+
class ProtocolVersionException < Exception
|
39
|
+
end
|
40
|
+
|
41
|
+
class LowVersionException < Exception
|
42
|
+
end
|
43
|
+
end
|
data/lib/paho_mqtt/handler.rb
CHANGED
@@ -21,9 +21,9 @@ module PahoMqtt
|
|
21
21
|
|
22
22
|
def initialize
|
23
23
|
@registered_callback = []
|
24
|
-
@last_ping_resp
|
25
|
-
@publisher
|
26
|
-
@subscriber
|
24
|
+
@last_ping_resp = -1
|
25
|
+
@publisher = nil
|
26
|
+
@subscriber = nil
|
27
27
|
end
|
28
28
|
|
29
29
|
def config_pubsub(publisher, subscriber)
|
@@ -52,7 +52,7 @@ module PahoMqtt
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def handle_packet(packet)
|
55
|
-
PahoMqtt.logger.info("New packet #{packet.class}
|
55
|
+
PahoMqtt.logger.info("New packet #{packet.class} received.") if PahoMqtt.logger?
|
56
56
|
type = packet_type(packet)
|
57
57
|
self.send("handle_#{type}", packet)
|
58
58
|
end
|
@@ -76,16 +76,17 @@ module PahoMqtt
|
|
76
76
|
PahoMqtt.logger.error("The topics where the callback is trying to be unregistered have been found nil.") if PahoMqtt.logger?
|
77
77
|
raise ArgumentError
|
78
78
|
end
|
79
|
-
@registered_callback.delete_if {|pair| pair.first == topic}
|
79
|
+
@registered_callback.delete_if { |pair| pair.first == topic }
|
80
80
|
MQTT_ERR_SUCCESS
|
81
81
|
end
|
82
82
|
|
83
83
|
def handle_connack(packet)
|
84
84
|
if packet.return_code == 0x00
|
85
|
-
PahoMqtt.logger.debug(
|
85
|
+
PahoMqtt.logger.debug(packet.return_msg) if PahoMqtt.logger?
|
86
86
|
handle_connack_accepted(packet.session_present)
|
87
87
|
else
|
88
|
-
|
88
|
+
PahoMqtt.logger.warm(packet.return_msg) if PahoMqtt.logger?
|
89
|
+
MQTT_CS_DISCONNECTED
|
89
90
|
end
|
90
91
|
@on_connack.call(packet) unless @on_connack.nil?
|
91
92
|
MQTT_CS_CONNECTED
|
@@ -99,7 +100,7 @@ module PahoMqtt
|
|
99
100
|
|
100
101
|
def new_session?(session_flag)
|
101
102
|
if !@clean_session && !session_flag
|
102
|
-
PahoMqtt.logger.debug("New session created for the client") if PahoMqtt.logger?
|
103
|
+
PahoMqtt.logger.debug("New session created for the client.") if PahoMqtt.logger?
|
103
104
|
end
|
104
105
|
end
|
105
106
|
|
@@ -121,9 +122,9 @@ module PahoMqtt
|
|
121
122
|
|
122
123
|
def handle_suback(packet)
|
123
124
|
max_qos = packet.return_codes
|
124
|
-
id
|
125
|
-
topics
|
126
|
-
topics
|
125
|
+
id = packet.id
|
126
|
+
topics = []
|
127
|
+
topics = @subscriber.add_subscription(max_qos, id, topics)
|
127
128
|
unless topics.empty?
|
128
129
|
@on_suback.call(topics) unless @on_suback.nil?
|
129
130
|
end
|
@@ -139,12 +140,12 @@ module PahoMqtt
|
|
139
140
|
end
|
140
141
|
|
141
142
|
def handle_publish(packet)
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
143
|
+
id = packet.id
|
144
|
+
qos = packet.qos
|
145
|
+
if @publisher.do_publish(qos, id) == MQTT_ERR_SUCCESS
|
146
|
+
@on_message.call(packet) unless @on_message.nil?
|
147
|
+
check_callback(packet)
|
148
|
+
end
|
148
149
|
end
|
149
150
|
|
150
151
|
def handle_puback(packet)
|
@@ -175,18 +176,6 @@ module PahoMqtt
|
|
175
176
|
end
|
176
177
|
end
|
177
178
|
|
178
|
-
def handle_connack_error(return_code)
|
179
|
-
if return_code == 0x01
|
180
|
-
raise LowVersionException
|
181
|
-
elsif CONNACK_ERROR_MESSAGE.has_key(return_code.to_sym)
|
182
|
-
PahoMqtt.logger.warm(CONNACK_ERRO_MESSAGE[return_code])
|
183
|
-
MQTT_CS_DISCONNECTED
|
184
|
-
else
|
185
|
-
PahoMqtt.logger("Unknown return code for CONNACK packet: #{return_code}")
|
186
|
-
raise PacketException
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
179
|
def on_connack(&block)
|
191
180
|
@on_connack = block if block_given?
|
192
181
|
@on_connack
|
@@ -264,9 +253,8 @@ module PahoMqtt
|
|
264
253
|
if PahoMqtt::PACKET_TYPES[3..13].include?(type)
|
265
254
|
type.to_s.split('::').last.downcase
|
266
255
|
else
|
267
|
-
|
268
|
-
|
269
|
-
raise PacketException
|
256
|
+
PahoMqtt.logger.error("Received an unexpeceted packet: #{packet}.") if PahoMqtt.logger?
|
257
|
+
raise PacketException.new('Invalid packet type id')
|
270
258
|
end
|
271
259
|
end
|
272
260
|
|
@@ -277,7 +265,7 @@ module PahoMqtt
|
|
277
265
|
end
|
278
266
|
unless callbacks.empty?
|
279
267
|
callbacks.each do |callback|
|
280
|
-
|
268
|
+
callback.call(packet)
|
281
269
|
end
|
282
270
|
end
|
283
271
|
end
|
@@ -36,8 +36,8 @@ module PahoMqtt
|
|
36
36
|
|
37
37
|
# Default attribute values
|
38
38
|
ATTR_DEFAULTS = {
|
39
|
-
:version
|
40
|
-
:id
|
39
|
+
:version => '3.1.0',
|
40
|
+
:id => 0,
|
41
41
|
:body_length => nil
|
42
42
|
}
|
43
43
|
|
@@ -47,6 +47,7 @@ module PahoMqtt
|
|
47
47
|
packet = create_from_header(
|
48
48
|
read_byte(socket)
|
49
49
|
)
|
50
|
+
|
50
51
|
unless packet.nil?
|
51
52
|
packet.validate_flags
|
52
53
|
|
@@ -59,22 +60,22 @@ module PahoMqtt
|
|
59
60
|
body_length += ((digit & 0x7F) * multiplier)
|
60
61
|
multiplier *= 0x80
|
61
62
|
pos += 1
|
62
|
-
end while ((digit & 0x80) != 0x00)
|
63
|
+
end while ((digit & 0x80) != 0x00) && pos <= 4
|
63
64
|
|
64
65
|
# Store the expected body length in the packet
|
65
66
|
packet.instance_variable_set('@body_length', body_length)
|
66
67
|
|
67
68
|
# Read in the packet body
|
68
|
-
packet.parse_body(
|
69
|
+
packet.parse_body(socket.read(body_length))
|
69
70
|
end
|
70
|
-
|
71
|
+
packet
|
71
72
|
end
|
72
73
|
|
73
74
|
# Parse buffer into new packet object
|
74
75
|
def self.parse(buffer)
|
75
76
|
packet = parse_header(buffer)
|
76
77
|
packet.parse_body(buffer)
|
77
|
-
|
78
|
+
packet
|
78
79
|
end
|
79
80
|
|
80
81
|
# Parse the header and create a new packet object of the correct type
|
@@ -82,7 +83,8 @@ module PahoMqtt
|
|
82
83
|
def self.parse_header(buffer)
|
83
84
|
# Check that the packet is a long as the minimum packet size
|
84
85
|
if buffer.bytesize < 2
|
85
|
-
raise
|
86
|
+
raise PahoMqtt::PacketFormatException.new(
|
87
|
+
"Invalid packet: less than 2 bytes long")
|
86
88
|
end
|
87
89
|
|
88
90
|
# Create a new packet object
|
@@ -96,13 +98,14 @@ module PahoMqtt
|
|
96
98
|
pos = 1
|
97
99
|
begin
|
98
100
|
if buffer.bytesize <= pos
|
99
|
-
raise
|
101
|
+
raise PahoMqtt::PacketFormatException.new(
|
102
|
+
"The packet length header is incomplete")
|
100
103
|
end
|
101
104
|
digit = bytes[pos]
|
102
105
|
body_length += ((digit & 0x7F) * multiplier)
|
103
106
|
multiplier *= 0x80
|
104
107
|
pos += 1
|
105
|
-
end while ((digit & 0x80) != 0x00)
|
108
|
+
end while ((digit & 0x80) != 0x00) && pos <= 4
|
106
109
|
|
107
110
|
# Store the expected body length in the packet
|
108
111
|
packet.instance_variable_set('@body_length', body_length)
|
@@ -110,7 +113,7 @@ module PahoMqtt
|
|
110
113
|
# Delete the fixed header from the raw packet passed in
|
111
114
|
buffer.slice!(0...pos)
|
112
115
|
|
113
|
-
|
116
|
+
packet
|
114
117
|
end
|
115
118
|
|
116
119
|
# Create a new packet object from the first byte of a MQTT packet
|
@@ -120,7 +123,8 @@ module PahoMqtt
|
|
120
123
|
type_id = ((byte & 0xF0) >> 4)
|
121
124
|
packet_class = PahoMqtt::PACKET_TYPES[type_id]
|
122
125
|
if packet_class.nil?
|
123
|
-
raise
|
126
|
+
raise PahoMqtt::PacketFormatException.new(
|
127
|
+
"Invalid packet type identifier: #{type_id}")
|
124
128
|
end
|
125
129
|
|
126
130
|
# Convert the last 4 bits of byte into array of true/false
|
@@ -141,7 +145,7 @@ module PahoMqtt
|
|
141
145
|
# Set packet attributes from a hash of attribute names and values
|
142
146
|
def update_attributes(attr={})
|
143
147
|
attr.each_pair do |k,v|
|
144
|
-
if v.is_a?(Array)
|
148
|
+
if v.is_a?(Array) || v.is_a?(Hash)
|
145
149
|
send("#{k}=", v.dup)
|
146
150
|
else
|
147
151
|
send("#{k}=", v)
|
@@ -153,9 +157,10 @@ module PahoMqtt
|
|
153
157
|
def type_id
|
154
158
|
index = PahoMqtt::PACKET_TYPES.index(self.class)
|
155
159
|
if index.nil?
|
156
|
-
raise
|
160
|
+
raise PahoMqtt::PacketFormatException.new(
|
161
|
+
"Invalid packet type: #{self.class}")
|
157
162
|
end
|
158
|
-
|
163
|
+
index
|
159
164
|
end
|
160
165
|
|
161
166
|
# Get the name of the packet type as a string in capitals
|
@@ -179,7 +184,8 @@ module PahoMqtt
|
|
179
184
|
# Parse the body (variable header and payload) of a packet
|
180
185
|
def parse_body(buffer)
|
181
186
|
if buffer.bytesize != body_length
|
182
|
-
raise
|
187
|
+
raise PahoMqtt::PacketFormatException.new(
|
188
|
+
"Failed to parse packet - input buffer (#{buffer.bytesize}) is not the same as the body length header (#{body_length})")
|
183
189
|
end
|
184
190
|
end
|
185
191
|
|
@@ -205,7 +211,8 @@ module PahoMqtt
|
|
205
211
|
# Check that that packet isn't too big
|
206
212
|
body_length = body.bytesize
|
207
213
|
if body_length > 268435455
|
208
|
-
raise
|
214
|
+
raise PahoMqtt::PacketFormatException.new(
|
215
|
+
"Error serialising packet: body is more than 256MB")
|
209
216
|
end
|
210
217
|
|
211
218
|
# Build up the body length field bytes
|
@@ -225,7 +232,8 @@ module PahoMqtt
|
|
225
232
|
# @private
|
226
233
|
def validate_flags
|
227
234
|
if flags != [false, false, false, false]
|
228
|
-
raise
|
235
|
+
raise PahoMqtt::PacketFormatException.new(
|
236
|
+
"Invalid flags in #{type_name} packet header")
|
229
237
|
end
|
230
238
|
end
|
231
239
|
|
@@ -243,7 +251,7 @@ module PahoMqtt
|
|
243
251
|
|
244
252
|
# Encode an array of bits and return them
|
245
253
|
def encode_bits(bits)
|
246
|
-
[bits.map{|b| b ? '1' : '0'}.join].pack('b*')
|
254
|
+
[bits.map { |b| b ? '1' : '0' }.join].pack('b*')
|
247
255
|
end
|
248
256
|
|
249
257
|
# Encode a 16-bit unsigned integer and return it
|
@@ -274,7 +282,7 @@ module PahoMqtt
|
|
274
282
|
|
275
283
|
# Remove 8 bits from the front of buffer
|
276
284
|
def shift_bits(buffer)
|
277
|
-
buffer.slice!(0...1).unpack('b8').first.split('').map {|b| b == '1'}
|
285
|
+
buffer.slice!(0...1).unpack('b8').first.split('').map { |b| b == '1' }
|
278
286
|
end
|
279
287
|
|
280
288
|
# Remove n bytes from the front of buffer
|
@@ -27,7 +27,7 @@ module PahoMqtt
|
|
27
27
|
attr_accessor :return_code
|
28
28
|
|
29
29
|
# Default attribute values
|
30
|
-
ATTR_DEFAULTS = {:return_code => 0x00}
|
30
|
+
ATTR_DEFAULTS = { :return_code => 0x00 }
|
31
31
|
|
32
32
|
# Create a new Client Connect packet
|
33
33
|
def initialize(args={})
|
@@ -54,9 +54,9 @@ module PahoMqtt
|
|
54
54
|
def return_msg
|
55
55
|
case return_code
|
56
56
|
when 0x00
|
57
|
-
"Connection
|
57
|
+
"Connection accepted"
|
58
58
|
when 0x01
|
59
|
-
|
59
|
+
raise LowVersionException
|
60
60
|
when 0x02
|
61
61
|
"Connection refused: client identifier rejected"
|
62
62
|
when 0x03
|
@@ -75,19 +75,21 @@ module PahoMqtt
|
|
75
75
|
body = ''
|
76
76
|
body += encode_bits(@connack_flags)
|
77
77
|
body += encode_bytes(@return_code.to_i)
|
78
|
-
|
78
|
+
body
|
79
79
|
end
|
80
80
|
|
81
81
|
# Parse the body (variable header and payload) of a Connect Acknowledgment packet
|
82
82
|
def parse_body(buffer)
|
83
83
|
super(buffer)
|
84
84
|
@connack_flags = shift_bits(buffer)
|
85
|
-
unless @connack_flags[1,7] == [false, false, false, false, false, false, false]
|
86
|
-
raise
|
85
|
+
unless @connack_flags[1, 7] == [false, false, false, false, false, false, false]
|
86
|
+
raise PacketFormatException.new(
|
87
|
+
"Invalid flags in Connack variable header")
|
87
88
|
end
|
88
89
|
@return_code = shift_byte(buffer)
|
89
90
|
unless buffer.empty?
|
90
|
-
raise
|
91
|
+
raise PacketFormatException.new(
|
92
|
+
"Extra bytes at end of Connect Acknowledgment packet")
|
91
93
|
end
|
92
94
|
end
|
93
95
|
|
@@ -55,29 +55,30 @@ module PahoMqtt
|
|
55
55
|
|
56
56
|
# Default attribute values
|
57
57
|
ATTR_DEFAULTS = {
|
58
|
-
:client_id
|
58
|
+
:client_id => nil,
|
59
59
|
:clean_session => true,
|
60
|
-
:keep_alive
|
61
|
-
:will_topic
|
62
|
-
:will_qos
|
63
|
-
:will_retain
|
64
|
-
:will_payload
|
65
|
-
:username
|
66
|
-
:password
|
60
|
+
:keep_alive => 15,
|
61
|
+
:will_topic => nil,
|
62
|
+
:will_qos => 0,
|
63
|
+
:will_retain => false,
|
64
|
+
:will_payload => '',
|
65
|
+
:username => nil,
|
66
|
+
:password => nil,
|
67
67
|
}
|
68
68
|
|
69
69
|
# Create a new Client Connect packet
|
70
70
|
def initialize(args={})
|
71
71
|
super(ATTR_DEFAULTS.merge(args))
|
72
72
|
|
73
|
-
if version == '3.1.0'
|
73
|
+
if version == '3.1.0' || version == '3.1'
|
74
74
|
self.protocol_name ||= 'MQIsdp'
|
75
75
|
self.protocol_level ||= 0x03
|
76
76
|
elsif version == '3.1.1'
|
77
77
|
self.protocol_name ||= 'MQTT'
|
78
78
|
self.protocol_level ||= 0x04
|
79
79
|
else
|
80
|
-
raise
|
80
|
+
raise PahoMqtt::PacketFormatException.new(
|
81
|
+
"Unsupported protocol version: #{version}")
|
81
82
|
end
|
82
83
|
end
|
83
84
|
|
@@ -88,7 +89,8 @@ module PahoMqtt
|
|
88
89
|
body += encode_string(@protocol_name)
|
89
90
|
body += encode_bytes(@protocol_level.to_i)
|
90
91
|
if @keep_alive < 0
|
91
|
-
raise
|
92
|
+
raise PahoMqtt::PacketFormatException.new(
|
93
|
+
"Invalid keep-alive value: cannot be less than 0")
|
92
94
|
end
|
93
95
|
|
94
96
|
body += encode_flags(@connect_flags)
|
@@ -106,10 +108,12 @@ module PahoMqtt
|
|
106
108
|
|
107
109
|
def check_version
|
108
110
|
if @version == '3.1.0'
|
109
|
-
if @client_id.nil?
|
110
|
-
raise
|
111
|
+
if @client_id.nil? || @client_id.bytesize < 1
|
112
|
+
raise PahoMqtt::PacketFormatException.new(
|
113
|
+
"Client identifier too short while serialising packet")
|
111
114
|
elsif @client_id.bytesize > 23
|
112
|
-
raise
|
115
|
+
raise PahoMqtt::PacketFormatException.new(
|
116
|
+
"Client identifier too long when serialising packet")
|
113
117
|
end
|
114
118
|
end
|
115
119
|
end
|
@@ -131,12 +135,13 @@ module PahoMqtt
|
|
131
135
|
super(buffer)
|
132
136
|
@protocol_name = shift_string(buffer)
|
133
137
|
@protocol_level = shift_byte(buffer).to_i
|
134
|
-
if @protocol_name == 'MQIsdp'
|
138
|
+
if @protocol_name == 'MQIsdp' && @protocol_level == 3
|
135
139
|
@version = '3.1.0'
|
136
|
-
elsif @protocol_name == 'MQTT'
|
140
|
+
elsif @protocol_name == 'MQTT' && @protocol_level == 4
|
137
141
|
@version = '3.1.1'
|
138
142
|
else
|
139
|
-
raise
|
143
|
+
raise PahoMqtt::PacketFormatException.new(
|
144
|
+
"Unsupported protocol: #{@protocol_name}/#{@protocol_level}")
|
140
145
|
end
|
141
146
|
|
142
147
|
@connect_flags = shift_byte(buffer)
|
@@ -155,10 +160,10 @@ module PahoMqtt
|
|
155
160
|
# The MQTT v3.1 specification says that the payload is a UTF-8 string
|
156
161
|
@will_payload = shift_string(buffer)
|
157
162
|
end
|
158
|
-
if ((@connect_flags & 0x80) >> 7) == 0x01
|
163
|
+
if ((@connect_flags & 0x80) >> 7) == 0x01 && buffer.bytesize > 0
|
159
164
|
@username = shift_string(buffer)
|
160
165
|
end
|
161
|
-
if ((@connect_flags & 0x40) >> 6) == 0x01
|
166
|
+
if ((@connect_flags & 0x40) >> 6) == 0x01 && buffer.bytesize > 0
|
162
167
|
@password = shift_string(buffer)
|
163
168
|
end
|
164
169
|
end
|