paho-mqtt 1.0.7 → 1.0.9
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 +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
|