mqtt 0.5.0 → 0.6.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.
- checksums.yaml +5 -5
- data/NEWS.md +1 -1
- data/README.md +1 -1
- data/lib/mqtt/client.rb +495 -479
- data/lib/mqtt/openssl_fix.rb +29 -0
- data/lib/mqtt/packet.rb +181 -234
- data/lib/mqtt/patches/string_encoding.rb +5 -7
- data/lib/mqtt/proxy.rb +81 -85
- data/lib/mqtt/sn/packet.rb +469 -512
- data/lib/mqtt/version.rb +1 -1
- data/lib/mqtt.rb +1 -3
- data/spec/mqtt_client_spec.rb +78 -7
- data/spec/mqtt_packet_spec.rb +8 -0
- metadata +24 -10
data/lib/mqtt/packet.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
# encoding: BINARY
|
2
2
|
|
3
3
|
module MQTT
|
4
|
-
|
5
4
|
# Class representing a MQTT Packet
|
6
5
|
# Performs binary encoding and decoding of headers
|
7
|
-
class
|
6
|
+
class Packet
|
8
7
|
# The version number of the MQTT protocol to use (default 3.1.0)
|
9
8
|
attr_accessor :version
|
10
9
|
|
@@ -36,27 +35,29 @@ module MQTT
|
|
36
35
|
multiplier = 1
|
37
36
|
body_length = 0
|
38
37
|
pos = 1
|
39
|
-
|
38
|
+
|
39
|
+
loop do
|
40
40
|
digit = read_byte(socket)
|
41
41
|
body_length += ((digit & 0x7F) * multiplier)
|
42
42
|
multiplier *= 0x80
|
43
43
|
pos += 1
|
44
|
-
|
44
|
+
break if (digit & 0x80).zero? || pos > 4
|
45
|
+
end
|
45
46
|
|
46
47
|
# Store the expected body length in the packet
|
47
48
|
packet.instance_variable_set('@body_length', body_length)
|
48
49
|
|
49
50
|
# Read in the packet body
|
50
|
-
packet.parse_body(
|
51
|
+
packet.parse_body(socket.read(body_length))
|
51
52
|
|
52
|
-
|
53
|
+
packet
|
53
54
|
end
|
54
55
|
|
55
56
|
# Parse buffer into new packet object
|
56
57
|
def self.parse(buffer)
|
57
58
|
packet = parse_header(buffer)
|
58
59
|
packet.parse_body(buffer)
|
59
|
-
|
60
|
+
packet
|
60
61
|
end
|
61
62
|
|
62
63
|
# Parse the header and create a new packet object of the correct type
|
@@ -64,11 +65,11 @@ module MQTT
|
|
64
65
|
def self.parse_header(buffer)
|
65
66
|
# Check that the packet is a long as the minimum packet size
|
66
67
|
if buffer.bytesize < 2
|
67
|
-
raise ProtocolException
|
68
|
+
raise ProtocolException, 'Invalid packet: less than 2 bytes long'
|
68
69
|
end
|
69
70
|
|
70
71
|
# Create a new packet object
|
71
|
-
bytes = buffer.unpack(
|
72
|
+
bytes = buffer.unpack('C5')
|
72
73
|
packet = create_from_header(bytes.first)
|
73
74
|
packet.validate_flags
|
74
75
|
|
@@ -76,15 +77,18 @@ module MQTT
|
|
76
77
|
body_length = 0
|
77
78
|
multiplier = 1
|
78
79
|
pos = 1
|
79
|
-
|
80
|
+
|
81
|
+
loop do
|
80
82
|
if buffer.bytesize <= pos
|
81
|
-
raise ProtocolException
|
83
|
+
raise ProtocolException, 'The packet length header is incomplete'
|
82
84
|
end
|
85
|
+
|
83
86
|
digit = bytes[pos]
|
84
87
|
body_length += ((digit & 0x7F) * multiplier)
|
85
88
|
multiplier *= 0x80
|
86
89
|
pos += 1
|
87
|
-
|
90
|
+
break if (digit & 0x80).zero? || pos > 4
|
91
|
+
end
|
88
92
|
|
89
93
|
# Store the expected body length in the packet
|
90
94
|
packet.instance_variable_set('@body_length', body_length)
|
@@ -92,7 +96,7 @@ module MQTT
|
|
92
96
|
# Delete the fixed header from the raw packet passed in
|
93
97
|
buffer.slice!(0...pos)
|
94
98
|
|
95
|
-
|
99
|
+
packet
|
96
100
|
end
|
97
101
|
|
98
102
|
# Create a new packet object from the first byte of a MQTT packet
|
@@ -101,27 +105,27 @@ module MQTT
|
|
101
105
|
type_id = ((byte & 0xF0) >> 4)
|
102
106
|
packet_class = MQTT::PACKET_TYPES[type_id]
|
103
107
|
if packet_class.nil?
|
104
|
-
raise ProtocolException
|
108
|
+
raise ProtocolException, "Invalid packet type identifier: #{type_id}"
|
105
109
|
end
|
106
110
|
|
107
111
|
# Convert the last 4 bits of byte into array of true/false
|
108
|
-
flags = (0..3).map { |i| byte & (2
|
112
|
+
flags = (0..3).map { |i| byte & (2**i) != 0 }
|
109
113
|
|
110
114
|
# Create a new packet object
|
111
115
|
packet_class.new(:flags => flags)
|
112
116
|
end
|
113
117
|
|
114
118
|
# Create a new empty packet
|
115
|
-
def initialize(args={})
|
119
|
+
def initialize(args = {})
|
116
120
|
# We must set flags before the other values
|
117
121
|
@flags = [false, false, false, false]
|
118
122
|
update_attributes(ATTR_DEFAULTS.merge(args))
|
119
123
|
end
|
120
124
|
|
121
125
|
# Set packet attributes from a hash of attribute names and values
|
122
|
-
def update_attributes(attr={})
|
123
|
-
attr.each_pair do |k,v|
|
124
|
-
if v.is_a?(Array)
|
126
|
+
def update_attributes(attr = {})
|
127
|
+
attr.each_pair do |k, v|
|
128
|
+
if v.is_a?(Array) || v.is_a?(Hash)
|
125
129
|
send("#{k}=", v.dup)
|
126
130
|
else
|
127
131
|
send("#{k}=", v)
|
@@ -132,10 +136,8 @@ module MQTT
|
|
132
136
|
# Get the identifer for this packet type
|
133
137
|
def type_id
|
134
138
|
index = MQTT::PACKET_TYPES.index(self.class)
|
135
|
-
if index.nil?
|
136
|
-
|
137
|
-
end
|
138
|
-
return index
|
139
|
+
raise "Invalid packet type: #{self.class}" if index.nil?
|
140
|
+
index
|
139
141
|
end
|
140
142
|
|
141
143
|
# Get the name of the packet type as a string in capitals
|
@@ -158,11 +160,9 @@ module MQTT
|
|
158
160
|
|
159
161
|
# Parse the body (variable header and payload) of a packet
|
160
162
|
def parse_body(buffer)
|
161
|
-
if buffer.bytesize
|
162
|
-
|
163
|
-
|
164
|
-
)
|
165
|
-
end
|
163
|
+
return if buffer.bytesize == body_length
|
164
|
+
|
165
|
+
raise ProtocolException, "Failed to parse packet - input buffer (#{buffer.bytesize}) is not the same as the body length header (#{body_length})"
|
166
166
|
end
|
167
167
|
|
168
168
|
# Get serialisation of packet's body (variable header and payload)
|
@@ -170,35 +170,35 @@ module MQTT
|
|
170
170
|
'' # No body by default
|
171
171
|
end
|
172
172
|
|
173
|
-
|
174
173
|
# Serialise the packet
|
175
174
|
def to_s
|
176
175
|
# Encode the fixed header
|
177
176
|
header = [
|
178
177
|
((type_id.to_i & 0x0F) << 4) |
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
178
|
+
(flags[3] ? 0x8 : 0x0) |
|
179
|
+
(flags[2] ? 0x4 : 0x0) |
|
180
|
+
(flags[1] ? 0x2 : 0x0) |
|
181
|
+
(flags[0] ? 0x1 : 0x0)
|
183
182
|
]
|
184
183
|
|
185
184
|
# Get the packet's variable header and payload
|
186
|
-
body =
|
185
|
+
body = encode_body
|
187
186
|
|
188
187
|
# Check that that packet isn't too big
|
189
188
|
body_length = body.bytesize
|
190
|
-
if body_length >
|
191
|
-
raise
|
189
|
+
if body_length > 268_435_455
|
190
|
+
raise 'Error serialising packet: body is more than 256MB'
|
192
191
|
end
|
193
192
|
|
194
193
|
# Build up the body length field bytes
|
195
|
-
|
194
|
+
loop do
|
196
195
|
digit = (body_length % 128)
|
197
196
|
body_length = body_length.div(128)
|
198
197
|
# if there are more digits to encode, set the top bit of this digit
|
199
|
-
digit |= 0x80 if
|
198
|
+
digit |= 0x80 if body_length > 0
|
200
199
|
header.push(digit)
|
201
|
-
|
200
|
+
break if body_length <= 0
|
201
|
+
end
|
202
202
|
|
203
203
|
# Convert header to binary and add on body
|
204
204
|
header.pack('C*') + body
|
@@ -207,9 +207,9 @@ module MQTT
|
|
207
207
|
# Check that fixed header flags are valid for types that don't use the flags
|
208
208
|
# @private
|
209
209
|
def validate_flags
|
210
|
-
if flags
|
211
|
-
|
212
|
-
|
210
|
+
return if flags == [false, false, false, false]
|
211
|
+
|
212
|
+
raise ProtocolException, "Invalid flags in #{type_name} packet header"
|
213
213
|
end
|
214
214
|
|
215
215
|
# Returns a human readable string
|
@@ -217,6 +217,14 @@ module MQTT
|
|
217
217
|
"\#<#{self.class}>"
|
218
218
|
end
|
219
219
|
|
220
|
+
# Read and unpack a single byte from a socket
|
221
|
+
def self.read_byte(socket)
|
222
|
+
byte = socket.getbyte
|
223
|
+
raise ProtocolException, 'Failed to read byte from socket' if byte.nil?
|
224
|
+
|
225
|
+
byte
|
226
|
+
end
|
227
|
+
|
220
228
|
protected
|
221
229
|
|
222
230
|
# Encode an array of bytes and return them
|
@@ -226,11 +234,12 @@ module MQTT
|
|
226
234
|
|
227
235
|
# Encode an array of bits and return them
|
228
236
|
def encode_bits(bits)
|
229
|
-
[bits.map{|b| b ? '1' : '0'}.join].pack('b*')
|
237
|
+
[bits.map { |b| b ? '1' : '0' }.join].pack('b*')
|
230
238
|
end
|
231
239
|
|
232
240
|
# Encode a 16-bit unsigned integer and return it
|
233
241
|
def encode_short(val)
|
242
|
+
raise 'Value too big for short' if val > 0xffff
|
234
243
|
[val.to_i].pack('n')
|
235
244
|
end
|
236
245
|
|
@@ -257,42 +266,26 @@ module MQTT
|
|
257
266
|
|
258
267
|
# Remove 8 bits from the front of buffer
|
259
268
|
def shift_bits(buffer)
|
260
|
-
buffer.slice!(0...1).unpack('b8').first.split('').map {|b| b == '1'}
|
269
|
+
buffer.slice!(0...1).unpack('b8').first.split('').map { |b| b == '1' }
|
261
270
|
end
|
262
271
|
|
263
272
|
# Remove n bytes from the front of buffer
|
264
|
-
def shift_data(buffer,bytes)
|
273
|
+
def shift_data(buffer, bytes)
|
265
274
|
buffer.slice!(0...bytes)
|
266
275
|
end
|
267
276
|
|
268
277
|
# Remove string from the front of buffer
|
269
278
|
def shift_string(buffer)
|
270
279
|
len = shift_short(buffer)
|
271
|
-
str = shift_data(buffer,len)
|
280
|
+
str = shift_data(buffer, len)
|
272
281
|
# Strings in MQTT v3.1 are all UTF-8
|
273
282
|
str.force_encoding('UTF-8')
|
274
283
|
end
|
275
284
|
|
276
|
-
|
277
|
-
private
|
278
|
-
|
279
|
-
# Read and unpack a single byte from a socket
|
280
|
-
def self.read_byte(socket)
|
281
|
-
byte = socket.read(1)
|
282
|
-
if byte.nil?
|
283
|
-
raise ProtocolException.new("Failed to read byte from socket")
|
284
|
-
end
|
285
|
-
byte.unpack('C').first
|
286
|
-
end
|
287
|
-
|
288
|
-
|
289
|
-
|
290
285
|
## PACKET SUBCLASSES ##
|
291
286
|
|
292
|
-
|
293
287
|
# Class representing an MQTT Publish message
|
294
288
|
class Publish < MQTT::Packet
|
295
|
-
|
296
289
|
# Duplicate delivery flag
|
297
290
|
attr_accessor :duplicate
|
298
291
|
|
@@ -315,7 +308,7 @@ module MQTT
|
|
315
308
|
}
|
316
309
|
|
317
310
|
# Create a new Publish packet
|
318
|
-
def initialize(args={})
|
311
|
+
def initialize(args = {})
|
319
312
|
super(ATTR_DEFAULTS.merge(args))
|
320
313
|
end
|
321
314
|
|
@@ -325,11 +318,7 @@ module MQTT
|
|
325
318
|
|
326
319
|
# Set the DUP flag (true/false)
|
327
320
|
def duplicate=(arg)
|
328
|
-
|
329
|
-
@flags[3] = (arg == 0x1)
|
330
|
-
else
|
331
|
-
@flags[3] = arg
|
332
|
-
end
|
321
|
+
@flags[3] = arg.is_a?(Integer) ? (arg == 0x1) : arg
|
333
322
|
end
|
334
323
|
|
335
324
|
def retain
|
@@ -338,11 +327,7 @@ module MQTT
|
|
338
327
|
|
339
328
|
# Set the retain flag (true/false)
|
340
329
|
def retain=(arg)
|
341
|
-
|
342
|
-
@flags[0] = (arg == 0x1)
|
343
|
-
else
|
344
|
-
@flags[0] = arg
|
345
|
-
end
|
330
|
+
@flags[0] = arg.is_a?(Integer) ? (arg == 0x1) : arg
|
346
331
|
end
|
347
332
|
|
348
333
|
def qos
|
@@ -352,61 +337,55 @@ module MQTT
|
|
352
337
|
# Set the Quality of Service level (0/1/2)
|
353
338
|
def qos=(arg)
|
354
339
|
@qos = arg.to_i
|
355
|
-
if @qos < 0
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
@flags[2] = (arg & 0x02 == 0x02)
|
360
|
-
end
|
340
|
+
raise "Invalid QoS value: #{@qos}" if @qos < 0 || @qos > 2
|
341
|
+
|
342
|
+
@flags[1] = (arg & 0x01 == 0x01)
|
343
|
+
@flags[2] = (arg & 0x02 == 0x02)
|
361
344
|
end
|
362
345
|
|
363
346
|
# Get serialisation of packet's body
|
364
347
|
def encode_body
|
365
348
|
body = ''
|
366
|
-
if @topic.nil?
|
367
|
-
raise
|
349
|
+
if @topic.nil? || @topic.to_s.empty?
|
350
|
+
raise 'Invalid topic name when serialising packet'
|
368
351
|
end
|
369
352
|
body += encode_string(@topic)
|
370
|
-
body += encode_short(@id) unless qos
|
353
|
+
body += encode_short(@id) unless qos.zero?
|
371
354
|
body += payload.to_s.dup.force_encoding('ASCII-8BIT')
|
372
|
-
|
355
|
+
body
|
373
356
|
end
|
374
357
|
|
375
358
|
# Parse the body (variable header and payload) of a Publish packet
|
376
359
|
def parse_body(buffer)
|
377
360
|
super(buffer)
|
378
361
|
@topic = shift_string(buffer)
|
379
|
-
@id = shift_short(buffer) unless qos
|
362
|
+
@id = shift_short(buffer) unless qos.zero?
|
380
363
|
@payload = buffer
|
381
364
|
end
|
382
365
|
|
383
366
|
# Check that fixed header flags are valid for this packet type
|
384
367
|
# @private
|
385
368
|
def validate_flags
|
386
|
-
if qos == 3
|
387
|
-
|
388
|
-
end
|
389
|
-
if qos == 0 and duplicate
|
390
|
-
raise ProtocolException.new("Invalid packet: DUP cannot be set for QoS 0")
|
391
|
-
end
|
369
|
+
raise ProtocolException, 'Invalid packet: QoS value of 3 is not allowed' if qos == 3
|
370
|
+
raise ProtocolException, 'Invalid packet: DUP cannot be set for QoS 0' if qos.zero? && duplicate
|
392
371
|
end
|
393
372
|
|
394
373
|
# Returns a human readable string, summarising the properties of the packet
|
395
374
|
def inspect
|
396
|
-
"\#<#{self.class}: "
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
375
|
+
"\#<#{self.class}: " \
|
376
|
+
"d#{duplicate ? '1' : '0'}, " \
|
377
|
+
"q#{qos}, " \
|
378
|
+
"r#{retain ? '1' : '0'}, " \
|
379
|
+
"m#{id}, " \
|
380
|
+
"'#{topic}', " \
|
381
|
+
"#{inspect_payload}>"
|
403
382
|
end
|
404
383
|
|
405
384
|
protected
|
406
385
|
|
407
386
|
def inspect_payload
|
408
387
|
str = payload.to_s
|
409
|
-
if str.bytesize < 16
|
388
|
+
if str.bytesize < 16 && str =~ /^[ -~]*$/
|
410
389
|
"'#{str}'"
|
411
390
|
else
|
412
391
|
"... (#{str.bytesize} bytes)"
|
@@ -459,39 +438,38 @@ module MQTT
|
|
459
438
|
:will_retain => false,
|
460
439
|
:will_payload => '',
|
461
440
|
:username => nil,
|
462
|
-
:password => nil
|
441
|
+
:password => nil
|
463
442
|
}
|
464
443
|
|
465
444
|
# Create a new Client Connect packet
|
466
|
-
def initialize(args={})
|
445
|
+
def initialize(args = {})
|
467
446
|
super(ATTR_DEFAULTS.merge(args))
|
468
447
|
|
469
|
-
if version == '3.1.0'
|
448
|
+
if version == '3.1.0' || version == '3.1'
|
470
449
|
self.protocol_name ||= 'MQIsdp'
|
471
450
|
self.protocol_level ||= 0x03
|
472
451
|
elsif version == '3.1.1'
|
473
452
|
self.protocol_name ||= 'MQTT'
|
474
453
|
self.protocol_level ||= 0x04
|
475
454
|
else
|
476
|
-
raise ArgumentError
|
455
|
+
raise ArgumentError, "Unsupported protocol version: #{version}"
|
477
456
|
end
|
478
457
|
end
|
479
458
|
|
480
459
|
# Get serialisation of packet's body
|
481
460
|
def encode_body
|
482
461
|
body = ''
|
462
|
+
|
483
463
|
if @version == '3.1.0'
|
484
|
-
if @client_id.nil?
|
485
|
-
|
486
|
-
elsif @client_id.bytesize > 23
|
487
|
-
raise "Client identifier too long when serialising packet"
|
488
|
-
end
|
464
|
+
raise 'Client identifier too short while serialising packet' if @client_id.nil? || @client_id.bytesize < 1
|
465
|
+
raise 'Client identifier too long when serialising packet' if @client_id.bytesize > 23
|
489
466
|
end
|
467
|
+
|
490
468
|
body += encode_string(@protocol_name)
|
491
469
|
body += encode_bytes(@protocol_level.to_i)
|
492
470
|
|
493
471
|
if @keep_alive < 0
|
494
|
-
raise
|
472
|
+
raise 'Invalid keep-alive value: cannot be less than 0'
|
495
473
|
end
|
496
474
|
|
497
475
|
# Set the Connect flags
|
@@ -513,7 +491,7 @@ module MQTT
|
|
513
491
|
end
|
514
492
|
body += encode_string(@username) unless @username.nil?
|
515
493
|
body += encode_string(@password) unless @password.nil?
|
516
|
-
|
494
|
+
body
|
517
495
|
end
|
518
496
|
|
519
497
|
# Parse the body (variable header and payload) of a Connect packet
|
@@ -521,14 +499,12 @@ module MQTT
|
|
521
499
|
super(buffer)
|
522
500
|
@protocol_name = shift_string(buffer)
|
523
501
|
@protocol_level = shift_byte(buffer).to_i
|
524
|
-
if @protocol_name == 'MQIsdp'
|
502
|
+
if @protocol_name == 'MQIsdp' && @protocol_level == 3
|
525
503
|
@version = '3.1.0'
|
526
|
-
elsif @protocol_name == 'MQTT'
|
504
|
+
elsif @protocol_name == 'MQTT' && @protocol_level == 4
|
527
505
|
@version = '3.1.1'
|
528
506
|
else
|
529
|
-
raise ProtocolException
|
530
|
-
"Unsupported protocol: #{@protocol_name}/#{@protocol_level}"
|
531
|
-
)
|
507
|
+
raise ProtocolException, "Unsupported protocol: #{@protocol_name}/#{@protocol_level}"
|
532
508
|
end
|
533
509
|
|
534
510
|
@connect_flags = shift_byte(buffer)
|
@@ -543,27 +519,26 @@ module MQTT
|
|
543
519
|
# The MQTT v3.1 specification says that the payload is a UTF-8 string
|
544
520
|
@will_payload = shift_string(buffer)
|
545
521
|
end
|
546
|
-
if ((@connect_flags & 0x80) >> 7) == 0x01
|
522
|
+
if ((@connect_flags & 0x80) >> 7) == 0x01 && buffer.bytesize > 0
|
547
523
|
@username = shift_string(buffer)
|
548
524
|
end
|
549
|
-
if ((@connect_flags & 0x40) >> 6) == 0x01
|
525
|
+
if ((@connect_flags & 0x40) >> 6) == 0x01 && buffer.bytesize > 0 # rubocop: disable Style/GuardClause
|
550
526
|
@password = shift_string(buffer)
|
551
527
|
end
|
552
528
|
end
|
553
529
|
|
554
530
|
# Returns a human readable string, summarising the properties of the packet
|
555
531
|
def inspect
|
556
|
-
str = "\#<#{self.class}: "
|
557
|
-
|
558
|
-
str +=
|
532
|
+
str = "\#<#{self.class}: " \
|
533
|
+
"keep_alive=#{keep_alive}"
|
534
|
+
str += ', clean' if clean_session
|
559
535
|
str += ", client_id='#{client_id}'"
|
560
536
|
str += ", username='#{username}'" unless username.nil?
|
561
|
-
str +=
|
562
|
-
str
|
537
|
+
str += ', password=...' unless password.nil?
|
538
|
+
str + '>'
|
563
539
|
end
|
564
540
|
|
565
541
|
# ---- Deprecated attributes and methods ---- #
|
566
|
-
public
|
567
542
|
|
568
543
|
# @deprecated Please use {#protocol_level} instead
|
569
544
|
def protocol_version
|
@@ -585,10 +560,10 @@ module MQTT
|
|
585
560
|
attr_accessor :return_code
|
586
561
|
|
587
562
|
# Default attribute values
|
588
|
-
ATTR_DEFAULTS = {:return_code => 0x00}
|
563
|
+
ATTR_DEFAULTS = { :return_code => 0x00 }
|
589
564
|
|
590
565
|
# Create a new Client Connect packet
|
591
|
-
def initialize(args={})
|
566
|
+
def initialize(args = {})
|
592
567
|
# We must set flags before other attributes
|
593
568
|
@connack_flags = [false, false, false, false, false, false, false, false]
|
594
569
|
super(ATTR_DEFAULTS.merge(args))
|
@@ -601,30 +576,26 @@ module MQTT
|
|
601
576
|
|
602
577
|
# Set the Session Present flag
|
603
578
|
def session_present=(arg)
|
604
|
-
|
605
|
-
@connack_flags[0] = (arg == 0x1)
|
606
|
-
else
|
607
|
-
@connack_flags[0] = arg
|
608
|
-
end
|
579
|
+
@connack_flags[0] = arg.is_a?(Integer) ? (arg == 0x1) : arg
|
609
580
|
end
|
610
581
|
|
611
582
|
# Get a string message corresponding to a return code
|
612
583
|
def return_msg
|
613
584
|
case return_code
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
585
|
+
when 0x00
|
586
|
+
'Connection Accepted'
|
587
|
+
when 0x01
|
588
|
+
'Connection refused: unacceptable protocol version'
|
589
|
+
when 0x02
|
590
|
+
'Connection refused: client identifier rejected'
|
591
|
+
when 0x03
|
592
|
+
'Connection refused: server unavailable'
|
593
|
+
when 0x04
|
594
|
+
'Connection refused: bad user name or password'
|
595
|
+
when 0x05
|
596
|
+
'Connection refused: not authorised'
|
597
|
+
else
|
598
|
+
"Connection refused: error code #{return_code}"
|
628
599
|
end
|
629
600
|
end
|
630
601
|
|
@@ -633,20 +604,20 @@ module MQTT
|
|
633
604
|
body = ''
|
634
605
|
body += encode_bits(@connack_flags)
|
635
606
|
body += encode_bytes(@return_code.to_i)
|
636
|
-
|
607
|
+
body
|
637
608
|
end
|
638
609
|
|
639
610
|
# Parse the body (variable header and payload) of a Connect Acknowledgment packet
|
640
611
|
def parse_body(buffer)
|
641
612
|
super(buffer)
|
642
613
|
@connack_flags = shift_bits(buffer)
|
643
|
-
unless @connack_flags[1,7] == [false, false, false, false, false, false, false]
|
644
|
-
raise ProtocolException
|
614
|
+
unless @connack_flags[1, 7] == [false, false, false, false, false, false, false]
|
615
|
+
raise ProtocolException, 'Invalid flags in Connack variable header'
|
645
616
|
end
|
646
617
|
@return_code = shift_byte(buffer)
|
647
|
-
|
648
|
-
|
649
|
-
end
|
618
|
+
|
619
|
+
return if buffer.empty?
|
620
|
+
raise ProtocolException, 'Extra bytes at end of Connect Acknowledgment packet'
|
650
621
|
end
|
651
622
|
|
652
623
|
# Returns a human readable string, summarising the properties of the packet
|
@@ -666,9 +637,9 @@ module MQTT
|
|
666
637
|
def parse_body(buffer)
|
667
638
|
super(buffer)
|
668
639
|
@id = shift_short(buffer)
|
669
|
-
|
670
|
-
|
671
|
-
end
|
640
|
+
|
641
|
+
return if buffer.empty?
|
642
|
+
raise ProtocolException, 'Extra bytes at end of Publish Acknowledgment packet'
|
672
643
|
end
|
673
644
|
|
674
645
|
# Returns a human readable string, summarising the properties of the packet
|
@@ -688,9 +659,9 @@ module MQTT
|
|
688
659
|
def parse_body(buffer)
|
689
660
|
super(buffer)
|
690
661
|
@id = shift_short(buffer)
|
691
|
-
|
692
|
-
|
693
|
-
end
|
662
|
+
|
663
|
+
return if buffer.empty?
|
664
|
+
raise ProtocolException, 'Extra bytes at end of Publish Received packet'
|
694
665
|
end
|
695
666
|
|
696
667
|
# Returns a human readable string, summarising the properties of the packet
|
@@ -701,14 +672,13 @@ module MQTT
|
|
701
672
|
|
702
673
|
# Class representing an MQTT Publish Release packet
|
703
674
|
class Pubrel < MQTT::Packet
|
704
|
-
|
705
675
|
# Default attribute values
|
706
676
|
ATTR_DEFAULTS = {
|
707
|
-
:flags => [false, true, false, false]
|
677
|
+
:flags => [false, true, false, false]
|
708
678
|
}
|
709
679
|
|
710
680
|
# Create a new Pubrel packet
|
711
|
-
def initialize(args={})
|
681
|
+
def initialize(args = {})
|
712
682
|
super(ATTR_DEFAULTS.merge(args))
|
713
683
|
end
|
714
684
|
|
@@ -721,17 +691,16 @@ module MQTT
|
|
721
691
|
def parse_body(buffer)
|
722
692
|
super(buffer)
|
723
693
|
@id = shift_short(buffer)
|
724
|
-
|
725
|
-
|
726
|
-
end
|
694
|
+
|
695
|
+
return if buffer.empty?
|
696
|
+
raise ProtocolException, 'Extra bytes at end of Publish Release packet'
|
727
697
|
end
|
728
698
|
|
729
699
|
# Check that fixed header flags are valid for this packet type
|
730
700
|
# @private
|
731
701
|
def validate_flags
|
732
|
-
if @flags
|
733
|
-
|
734
|
-
end
|
702
|
+
return if @flags == [false, true, false, false]
|
703
|
+
raise ProtocolException, 'Invalid flags in PUBREL packet header'
|
735
704
|
end
|
736
705
|
|
737
706
|
# Returns a human readable string, summarising the properties of the packet
|
@@ -751,9 +720,9 @@ module MQTT
|
|
751
720
|
def parse_body(buffer)
|
752
721
|
super(buffer)
|
753
722
|
@id = shift_short(buffer)
|
754
|
-
|
755
|
-
|
756
|
-
end
|
723
|
+
|
724
|
+
return if buffer.empty?
|
725
|
+
raise ProtocolException, 'Extra bytes at end of Publish Complete packet'
|
757
726
|
end
|
758
727
|
|
759
728
|
# Returns a human readable string, summarising the properties of the packet
|
@@ -770,11 +739,11 @@ module MQTT
|
|
770
739
|
# Default attribute values
|
771
740
|
ATTR_DEFAULTS = {
|
772
741
|
:topics => [],
|
773
|
-
:flags => [false, true, false, false]
|
742
|
+
:flags => [false, true, false, false]
|
774
743
|
}
|
775
744
|
|
776
745
|
# Create a new Subscribe packet
|
777
|
-
def initialize(args={})
|
746
|
+
def initialize(args = {})
|
778
747
|
super(ATTR_DEFAULTS.merge(args))
|
779
748
|
end
|
780
749
|
|
@@ -792,14 +761,10 @@ module MQTT
|
|
792
761
|
#
|
793
762
|
def topics=(value)
|
794
763
|
# Get input into a consistent state
|
795
|
-
|
796
|
-
input = value.flatten
|
797
|
-
else
|
798
|
-
input = [value]
|
799
|
-
end
|
764
|
+
input = value.is_a?(Array) ? value.flatten : [value]
|
800
765
|
|
801
766
|
@topics = []
|
802
|
-
|
767
|
+
until input.empty?
|
803
768
|
item = input.shift
|
804
769
|
if item.is_a?(Hash)
|
805
770
|
# Convert hash into an ordered array of arrays
|
@@ -808,9 +773,9 @@ module MQTT
|
|
808
773
|
# Peek at the next item in the array, and remove it if it is an integer
|
809
774
|
if input.first.is_a?(Integer)
|
810
775
|
qos = input.shift
|
811
|
-
@topics << [item,qos]
|
776
|
+
@topics << [item, qos]
|
812
777
|
else
|
813
|
-
@topics << [item,0]
|
778
|
+
@topics << [item, 0]
|
814
779
|
end
|
815
780
|
else
|
816
781
|
# Meh?
|
@@ -822,15 +787,13 @@ module MQTT
|
|
822
787
|
|
823
788
|
# Get serialisation of packet's body
|
824
789
|
def encode_body
|
825
|
-
if @topics.empty?
|
826
|
-
raise "no topics given when serialising packet"
|
827
|
-
end
|
790
|
+
raise 'no topics given when serialising packet' if @topics.empty?
|
828
791
|
body = encode_short(@id)
|
829
792
|
topics.each do |item|
|
830
793
|
body += encode_string(item[0])
|
831
794
|
body += encode_bytes(item[1])
|
832
795
|
end
|
833
|
-
|
796
|
+
body
|
834
797
|
end
|
835
798
|
|
836
799
|
# Parse the body (variable header and payload) of a packet
|
@@ -838,26 +801,25 @@ module MQTT
|
|
838
801
|
super(buffer)
|
839
802
|
@id = shift_short(buffer)
|
840
803
|
@topics = []
|
841
|
-
while
|
804
|
+
while buffer.bytesize > 0
|
842
805
|
topic_name = shift_string(buffer)
|
843
806
|
topic_qos = shift_byte(buffer)
|
844
|
-
@topics << [topic_name,topic_qos]
|
807
|
+
@topics << [topic_name, topic_qos]
|
845
808
|
end
|
846
809
|
end
|
847
810
|
|
848
811
|
# Check that fixed header flags are valid for this packet type
|
849
812
|
# @private
|
850
813
|
def validate_flags
|
851
|
-
if @flags
|
852
|
-
|
853
|
-
end
|
814
|
+
return if @flags == [false, true, false, false]
|
815
|
+
raise ProtocolException, 'Invalid flags in SUBSCRIBE packet header'
|
854
816
|
end
|
855
817
|
|
856
818
|
# Returns a human readable string, summarising the properties of the packet
|
857
819
|
def inspect
|
858
820
|
_str = "\#<#{self.class}: 0x%2.2X, %s>" % [
|
859
821
|
id,
|
860
|
-
topics.map {|t| "'#{t[0]}':#{t[1]}"}.join(', ')
|
822
|
+
topics.map { |t| "'#{t[0]}':#{t[1]}" }.join(', ')
|
861
823
|
]
|
862
824
|
end
|
863
825
|
end
|
@@ -869,11 +831,11 @@ module MQTT
|
|
869
831
|
|
870
832
|
# Default attribute values
|
871
833
|
ATTR_DEFAULTS = {
|
872
|
-
:return_codes => []
|
834
|
+
:return_codes => []
|
873
835
|
}
|
874
836
|
|
875
837
|
# Create a new Subscribe Acknowledgment packet
|
876
|
-
def initialize(args={})
|
838
|
+
def initialize(args = {})
|
877
839
|
super(ATTR_DEFAULTS.merge(args))
|
878
840
|
end
|
879
841
|
|
@@ -885,36 +847,33 @@ module MQTT
|
|
885
847
|
elsif value.is_a?(Integer)
|
886
848
|
@return_codes = [value]
|
887
849
|
else
|
888
|
-
raise
|
850
|
+
raise 'return_codes should be an integer or an array of return codes'
|
889
851
|
end
|
890
852
|
end
|
891
853
|
|
892
854
|
# Get serialisation of packet's body
|
893
855
|
def encode_body
|
894
856
|
if @return_codes.empty?
|
895
|
-
raise
|
857
|
+
raise 'no granted QoS given when serialising packet'
|
896
858
|
end
|
897
859
|
body = encode_short(@id)
|
898
860
|
return_codes.each { |qos| body += encode_bytes(qos) }
|
899
|
-
|
861
|
+
body
|
900
862
|
end
|
901
863
|
|
902
864
|
# Parse the body (variable header and payload) of a packet
|
903
865
|
def parse_body(buffer)
|
904
866
|
super(buffer)
|
905
867
|
@id = shift_short(buffer)
|
906
|
-
|
907
|
-
@return_codes << shift_byte(buffer)
|
908
|
-
end
|
868
|
+
@return_codes << shift_byte(buffer) while buffer.bytesize > 0
|
909
869
|
end
|
910
870
|
|
911
871
|
# Returns a human readable string, summarising the properties of the packet
|
912
872
|
def inspect
|
913
|
-
"\#<#{self.class}: 0x%2.2X, rc=%s>" % [id, return_codes.map{|rc|
|
873
|
+
"\#<#{self.class}: 0x%2.2X, rc=%s>" % [id, return_codes.map { |rc| '0x%2.2X' % rc }.join(',')]
|
914
874
|
end
|
915
875
|
|
916
876
|
# ---- Deprecated attributes and methods ---- #
|
917
|
-
public
|
918
877
|
|
919
878
|
# @deprecated Please use {#return_codes} instead
|
920
879
|
def granted_qos
|
@@ -935,55 +894,46 @@ module MQTT
|
|
935
894
|
# Default attribute values
|
936
895
|
ATTR_DEFAULTS = {
|
937
896
|
:topics => [],
|
938
|
-
:flags => [false, true, false, false]
|
897
|
+
:flags => [false, true, false, false]
|
939
898
|
}
|
940
899
|
|
941
900
|
# Create a new Unsubscribe packet
|
942
|
-
def initialize(args={})
|
901
|
+
def initialize(args = {})
|
943
902
|
super(ATTR_DEFAULTS.merge(args))
|
944
903
|
end
|
945
904
|
|
946
905
|
# Set one or more topic paths to unsubscribe from
|
947
906
|
def topics=(value)
|
948
|
-
|
949
|
-
@topics = value
|
950
|
-
else
|
951
|
-
@topics = [value]
|
952
|
-
end
|
907
|
+
@topics = value.is_a?(Array) ? value : [value]
|
953
908
|
end
|
954
909
|
|
955
910
|
# Get serialisation of packet's body
|
956
911
|
def encode_body
|
957
|
-
if @topics.empty?
|
958
|
-
raise "no topics given when serialising packet"
|
959
|
-
end
|
912
|
+
raise 'no topics given when serialising packet' if @topics.empty?
|
960
913
|
body = encode_short(@id)
|
961
914
|
topics.each { |topic| body += encode_string(topic) }
|
962
|
-
|
915
|
+
body
|
963
916
|
end
|
964
917
|
|
965
918
|
# Parse the body (variable header and payload) of a packet
|
966
919
|
def parse_body(buffer)
|
967
920
|
super(buffer)
|
968
921
|
@id = shift_short(buffer)
|
969
|
-
|
970
|
-
@topics << shift_string(buffer)
|
971
|
-
end
|
922
|
+
@topics << shift_string(buffer) while buffer.bytesize > 0
|
972
923
|
end
|
973
924
|
|
974
925
|
# Check that fixed header flags are valid for this packet type
|
975
926
|
# @private
|
976
927
|
def validate_flags
|
977
|
-
if @flags
|
978
|
-
|
979
|
-
end
|
928
|
+
return if @flags == [false, true, false, false]
|
929
|
+
raise ProtocolException, 'Invalid flags in UNSUBSCRIBE packet header'
|
980
930
|
end
|
981
931
|
|
982
932
|
# Returns a human readable string, summarising the properties of the packet
|
983
933
|
def inspect
|
984
934
|
"\#<#{self.class}: 0x%2.2X, %s>" % [
|
985
935
|
id,
|
986
|
-
topics.map {|t| "'#{t}'"}.join(', ')
|
936
|
+
topics.map { |t| "'#{t}'" }.join(', ')
|
987
937
|
]
|
988
938
|
end
|
989
939
|
end
|
@@ -991,7 +941,7 @@ module MQTT
|
|
991
941
|
# Class representing an MQTT Unsubscribe Acknowledgment packet
|
992
942
|
class Unsuback < MQTT::Packet
|
993
943
|
# Create a new Unsubscribe Acknowledgment packet
|
994
|
-
def initialize(args={})
|
944
|
+
def initialize(args = {})
|
995
945
|
super(args)
|
996
946
|
end
|
997
947
|
|
@@ -1004,9 +954,9 @@ module MQTT
|
|
1004
954
|
def parse_body(buffer)
|
1005
955
|
super(buffer)
|
1006
956
|
@id = shift_short(buffer)
|
1007
|
-
|
1008
|
-
|
1009
|
-
end
|
957
|
+
|
958
|
+
return if buffer.empty?
|
959
|
+
raise ProtocolException, 'Extra bytes at end of Unsubscribe Acknowledgment packet'
|
1010
960
|
end
|
1011
961
|
|
1012
962
|
# Returns a human readable string, summarising the properties of the packet
|
@@ -1018,52 +968,51 @@ module MQTT
|
|
1018
968
|
# Class representing an MQTT Ping Request packet
|
1019
969
|
class Pingreq < MQTT::Packet
|
1020
970
|
# Create a new Ping Request packet
|
1021
|
-
def initialize(args={})
|
971
|
+
def initialize(args = {})
|
1022
972
|
super(args)
|
1023
973
|
end
|
1024
974
|
|
1025
975
|
# Check the body
|
1026
976
|
def parse_body(buffer)
|
1027
977
|
super(buffer)
|
1028
|
-
|
1029
|
-
|
1030
|
-
end
|
978
|
+
|
979
|
+
return if buffer.empty?
|
980
|
+
raise ProtocolException, 'Extra bytes at end of Ping Request packet'
|
1031
981
|
end
|
1032
982
|
end
|
1033
983
|
|
1034
984
|
# Class representing an MQTT Ping Response packet
|
1035
985
|
class Pingresp < MQTT::Packet
|
1036
986
|
# Create a new Ping Response packet
|
1037
|
-
def initialize(args={})
|
987
|
+
def initialize(args = {})
|
1038
988
|
super(args)
|
1039
989
|
end
|
1040
990
|
|
1041
991
|
# Check the body
|
1042
992
|
def parse_body(buffer)
|
1043
993
|
super(buffer)
|
1044
|
-
|
1045
|
-
|
1046
|
-
end
|
994
|
+
|
995
|
+
return if buffer.empty?
|
996
|
+
raise ProtocolException, 'Extra bytes at end of Ping Response packet'
|
1047
997
|
end
|
1048
998
|
end
|
1049
999
|
|
1050
1000
|
# Class representing an MQTT Client Disconnect packet
|
1051
1001
|
class Disconnect < MQTT::Packet
|
1052
1002
|
# Create a new Client Disconnect packet
|
1053
|
-
def initialize(args={})
|
1003
|
+
def initialize(args = {})
|
1054
1004
|
super(args)
|
1055
1005
|
end
|
1056
1006
|
|
1057
1007
|
# Check the body
|
1058
1008
|
def parse_body(buffer)
|
1059
1009
|
super(buffer)
|
1060
|
-
|
1061
|
-
|
1062
|
-
end
|
1010
|
+
|
1011
|
+
return if buffer.empty?
|
1012
|
+
raise ProtocolException, 'Extra bytes at end of Disconnect packet'
|
1063
1013
|
end
|
1064
1014
|
end
|
1065
1015
|
|
1066
|
-
|
1067
1016
|
# ---- Deprecated attributes and methods ---- #
|
1068
1017
|
public
|
1069
1018
|
|
@@ -1078,7 +1027,6 @@ module MQTT
|
|
1078
1027
|
end
|
1079
1028
|
end
|
1080
1029
|
|
1081
|
-
|
1082
1030
|
# An enumeration of the MQTT packet types
|
1083
1031
|
PACKET_TYPES = [
|
1084
1032
|
nil,
|
@@ -1098,5 +1046,4 @@ module MQTT
|
|
1098
1046
|
MQTT::Packet::Disconnect,
|
1099
1047
|
nil
|
1100
1048
|
]
|
1101
|
-
|
1102
1049
|
end
|