mqtt 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/NEWS.md +22 -2
- data/README.md +126 -7
- data/lib/mqtt.rb +5 -5
- data/lib/mqtt/client.rb +92 -55
- data/lib/mqtt/packet.rb +305 -185
- data/lib/mqtt/proxy.rb +28 -28
- data/lib/mqtt/version.rb +2 -1
- data/spec/mqtt_client_spec.rb +252 -175
- data/spec/mqtt_packet_spec.rb +823 -401
- data/spec/mqtt_version_spec.rb +3 -3
- data/spec/zz_client_integration_spec.rb +16 -16
- metadata +102 -85
data/lib/mqtt/packet.rb
CHANGED
@@ -5,23 +5,22 @@ module MQTT
|
|
5
5
|
# Class representing a MQTT Packet
|
6
6
|
# Performs binary encoding and decoding of headers
|
7
7
|
class MQTT::Packet
|
8
|
-
#
|
9
|
-
|
8
|
+
# The version number of the MQTT protocol to use (default 3.1.0)
|
9
|
+
attr_accessor :version
|
10
10
|
|
11
|
-
#
|
12
|
-
|
11
|
+
# Identifier to link related control packets together
|
12
|
+
attr_accessor :id
|
13
13
|
|
14
|
-
#
|
15
|
-
|
14
|
+
# Array of 4 bits in the fixed header
|
15
|
+
attr_accessor :flags
|
16
16
|
|
17
17
|
# The length of the parsed packet body
|
18
18
|
attr_reader :body_length
|
19
19
|
|
20
20
|
# Default attribute values
|
21
21
|
ATTR_DEFAULTS = {
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:retain => false,
|
22
|
+
:version => '3.1.0',
|
23
|
+
:id => 0,
|
25
24
|
:body_length => nil
|
26
25
|
}
|
27
26
|
|
@@ -31,6 +30,7 @@ module MQTT
|
|
31
30
|
packet = create_from_header(
|
32
31
|
read_byte(socket)
|
33
32
|
)
|
33
|
+
packet.validate_flags
|
34
34
|
|
35
35
|
# Read in the packet length
|
36
36
|
multiplier = 1
|
@@ -70,6 +70,7 @@ module MQTT
|
|
70
70
|
# Create a new packet object
|
71
71
|
bytes = buffer.unpack("C5")
|
72
72
|
packet = create_from_header(bytes.first)
|
73
|
+
packet.validate_flags
|
73
74
|
|
74
75
|
# Parse the packet length
|
75
76
|
body_length = 0
|
@@ -103,23 +104,28 @@ module MQTT
|
|
103
104
|
raise ProtocolException.new("Invalid packet type identifier: #{type_id}")
|
104
105
|
end
|
105
106
|
|
107
|
+
# Convert the last 4 bits of byte into array of true/false
|
108
|
+
flags = (0..3).map { |i| byte & (2 ** i) != 0 }
|
109
|
+
|
106
110
|
# Create a new packet object
|
107
|
-
packet_class.new(
|
108
|
-
:duplicate => ((byte & 0x08) >> 3) == 0x01,
|
109
|
-
:qos => ((byte & 0x06) >> 1),
|
110
|
-
:retain => ((byte & 0x01) >> 0) == 0x01
|
111
|
-
)
|
111
|
+
packet_class.new(:flags => flags)
|
112
112
|
end
|
113
113
|
|
114
114
|
# Create a new empty packet
|
115
115
|
def initialize(args={})
|
116
|
+
# We must set flags before the other values
|
117
|
+
@flags = [false, false, false, false]
|
116
118
|
update_attributes(ATTR_DEFAULTS.merge(args))
|
117
119
|
end
|
118
120
|
|
119
121
|
# Set packet attributes from a hash of attribute names and values
|
120
122
|
def update_attributes(attr={})
|
121
123
|
attr.each_pair do |k,v|
|
122
|
-
|
124
|
+
if v.is_a?(Array) or v.is_a?(Hash)
|
125
|
+
send("#{k}=", v.dup)
|
126
|
+
else
|
127
|
+
send("#{k}=", v)
|
128
|
+
end
|
123
129
|
end
|
124
130
|
end
|
125
131
|
|
@@ -132,30 +138,17 @@ module MQTT
|
|
132
138
|
return index
|
133
139
|
end
|
134
140
|
|
135
|
-
#
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
end
|
141
|
+
# Get the name of the packet type as a string in capitals
|
142
|
+
# (like the MQTT specification uses)
|
143
|
+
#
|
144
|
+
# Example: CONNACK
|
145
|
+
def type_name
|
146
|
+
self.class.name.split('::').last.upcase
|
142
147
|
end
|
143
148
|
|
144
|
-
# Set the
|
145
|
-
def
|
146
|
-
|
147
|
-
@retain = (arg != 0)
|
148
|
-
else
|
149
|
-
@retain = arg
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
# Set the Quality of Service level (0/1/2)
|
154
|
-
def qos=(arg)
|
155
|
-
@qos = arg.to_i
|
156
|
-
if @qos < 0 or @qos > 2
|
157
|
-
raise "Invalid QoS value: #{@qos}"
|
158
|
-
end
|
149
|
+
# Set the protocol version number
|
150
|
+
def version=(arg)
|
151
|
+
@version = arg.to_s
|
159
152
|
end
|
160
153
|
|
161
154
|
# Set the length of the packet body
|
@@ -183,9 +176,10 @@ module MQTT
|
|
183
176
|
# Encode the fixed header
|
184
177
|
header = [
|
185
178
|
((type_id.to_i & 0x0F) << 4) |
|
186
|
-
(
|
187
|
-
(
|
188
|
-
(
|
179
|
+
(flags[3] ? 0x8 : 0x0) |
|
180
|
+
(flags[2] ? 0x4 : 0x0) |
|
181
|
+
(flags[1] ? 0x2 : 0x0) |
|
182
|
+
(flags[0] ? 0x1 : 0x0)
|
189
183
|
]
|
190
184
|
|
191
185
|
# Get the packet's variable header and payload
|
@@ -210,6 +204,14 @@ module MQTT
|
|
210
204
|
header.pack('C*') + body
|
211
205
|
end
|
212
206
|
|
207
|
+
# Check that fixed header flags are valid for types that don't use the flags
|
208
|
+
# @private
|
209
|
+
def validate_flags
|
210
|
+
if flags != [false, false, false, false]
|
211
|
+
raise ProtocolException.new("Invalid flags in #{type_name} packet header")
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
213
215
|
# Returns a human readable string
|
214
216
|
def inspect
|
215
217
|
"\#<#{self.class}>"
|
@@ -222,6 +224,11 @@ module MQTT
|
|
222
224
|
bytes.pack('C*')
|
223
225
|
end
|
224
226
|
|
227
|
+
# Encode an array of bits and return them
|
228
|
+
def encode_bits(bits)
|
229
|
+
[bits.map{|b| b ? '1' : '0'}.join].pack('b*')
|
230
|
+
end
|
231
|
+
|
225
232
|
# Encode a 16-bit unsigned integer and return it
|
226
233
|
def encode_short(val)
|
227
234
|
[val.to_i].pack('n')
|
@@ -248,6 +255,11 @@ module MQTT
|
|
248
255
|
buffer.slice!(0...1).unpack('C').first
|
249
256
|
end
|
250
257
|
|
258
|
+
# Remove 8 bits from the front of buffer
|
259
|
+
def shift_bits(buffer)
|
260
|
+
buffer.slice!(0...1).unpack('b8').first.split('').map {|b| b == '1'}
|
261
|
+
end
|
262
|
+
|
251
263
|
# Remove n bytes from the front of buffer
|
252
264
|
def shift_data(buffer,bytes)
|
253
265
|
buffer.slice!(0...bytes)
|
@@ -280,21 +292,26 @@ module MQTT
|
|
280
292
|
|
281
293
|
# Class representing an MQTT Publish message
|
282
294
|
class Publish < MQTT::Packet
|
295
|
+
|
296
|
+
# Duplicate delivery flag
|
297
|
+
attr_accessor :duplicate
|
298
|
+
|
299
|
+
# Retain flag
|
300
|
+
attr_accessor :retain
|
301
|
+
|
302
|
+
# Quality of Service level (0, 1, 2)
|
303
|
+
attr_accessor :qos
|
304
|
+
|
283
305
|
# The topic name to publish to
|
284
306
|
attr_accessor :topic
|
285
|
-
|
286
|
-
# Identifier for an individual publishing flow
|
287
|
-
# Only required in PUBLISH Packets where the QoS level is 1 or 2
|
288
|
-
attr_accessor :message_id
|
289
|
-
|
307
|
+
|
290
308
|
# The data to be published
|
291
309
|
attr_accessor :payload
|
292
310
|
|
293
311
|
# Default attribute values
|
294
312
|
ATTR_DEFAULTS = {
|
295
|
-
|
296
|
-
|
297
|
-
:payload => ''
|
313
|
+
:topic => nil,
|
314
|
+
:payload => ''
|
298
315
|
}
|
299
316
|
|
300
317
|
# Create a new Publish packet
|
@@ -302,6 +319,47 @@ module MQTT
|
|
302
319
|
super(ATTR_DEFAULTS.merge(args))
|
303
320
|
end
|
304
321
|
|
322
|
+
def duplicate
|
323
|
+
@flags[3]
|
324
|
+
end
|
325
|
+
|
326
|
+
# Set the DUP flag (true/false)
|
327
|
+
def duplicate=(arg)
|
328
|
+
if arg.kind_of?(Integer)
|
329
|
+
@flags[3] = (arg == 0x1)
|
330
|
+
else
|
331
|
+
@flags[3] = arg
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def retain
|
336
|
+
@flags[0]
|
337
|
+
end
|
338
|
+
|
339
|
+
# Set the retain flag (true/false)
|
340
|
+
def retain=(arg)
|
341
|
+
if arg.kind_of?(Integer)
|
342
|
+
@flags[0] = (arg == 0x1)
|
343
|
+
else
|
344
|
+
@flags[0] = arg
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def qos
|
349
|
+
(@flags[1] ? 0x01 : 0x00) | (@flags[2] ? 0x02 : 0x00)
|
350
|
+
end
|
351
|
+
|
352
|
+
# Set the Quality of Service level (0/1/2)
|
353
|
+
def qos=(arg)
|
354
|
+
@qos = arg.to_i
|
355
|
+
if @qos < 0 or @qos > 2
|
356
|
+
raise "Invalid QoS value: #{@qos}"
|
357
|
+
else
|
358
|
+
@flags[1] = (arg & 0x01 == 0x01)
|
359
|
+
@flags[2] = (arg & 0x02 == 0x02)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
305
363
|
# Get serialisation of packet's body
|
306
364
|
def encode_body
|
307
365
|
body = ''
|
@@ -309,7 +367,7 @@ module MQTT
|
|
309
367
|
raise "Invalid topic name when serialising packet"
|
310
368
|
end
|
311
369
|
body += encode_string(@topic)
|
312
|
-
body += encode_short(@
|
370
|
+
body += encode_short(@id) unless qos == 0
|
313
371
|
body += payload.to_s.force_encoding('ASCII-8BIT')
|
314
372
|
return body
|
315
373
|
end
|
@@ -318,25 +376,37 @@ module MQTT
|
|
318
376
|
def parse_body(buffer)
|
319
377
|
super(buffer)
|
320
378
|
@topic = shift_string(buffer)
|
321
|
-
@
|
379
|
+
@id = shift_short(buffer) unless qos == 0
|
322
380
|
@payload = buffer
|
323
381
|
end
|
324
382
|
|
383
|
+
# Check that fixed header flags are valid for this packet type
|
384
|
+
# @private
|
385
|
+
def validate_flags
|
386
|
+
if qos == 3
|
387
|
+
raise ProtocolException.new("Invalid packet: QoS value of 3 is not allowed")
|
388
|
+
end
|
389
|
+
if qos == 0 and duplicate
|
390
|
+
raise ProtocolException.new("Invalid packet: DUP cannot be set for QoS 0")
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
325
394
|
# Returns a human readable string, summarising the properties of the packet
|
326
395
|
def inspect
|
327
396
|
"\#<#{self.class}: " +
|
328
397
|
"d#{duplicate ? '1' : '0'}, " +
|
329
398
|
"q#{qos}, " +
|
330
399
|
"r#{retain ? '1' : '0'}, " +
|
331
|
-
"m#{
|
400
|
+
"m#{id}, " +
|
332
401
|
"'#{topic}', " +
|
333
402
|
"#{inspect_payload}>"
|
334
403
|
end
|
335
404
|
|
336
405
|
protected
|
406
|
+
|
337
407
|
def inspect_payload
|
338
408
|
str = payload.to_s
|
339
|
-
if str.bytesize < 16
|
409
|
+
if str.bytesize < 16 and str =~ /^[ -~]*$/
|
340
410
|
"'#{str}'"
|
341
411
|
else
|
342
412
|
"... (#{str.bytesize} bytes)"
|
@@ -346,43 +416,41 @@ module MQTT
|
|
346
416
|
|
347
417
|
# Class representing an MQTT Connect Packet
|
348
418
|
class Connect < MQTT::Packet
|
349
|
-
# The name of the protocol
|
419
|
+
# The name of the protocol
|
350
420
|
attr_accessor :protocol_name
|
351
421
|
|
352
|
-
# The version number of the protocol
|
353
|
-
attr_accessor :
|
422
|
+
# The version number of the protocol
|
423
|
+
attr_accessor :protocol_level
|
354
424
|
|
355
425
|
# The client identifier string
|
356
426
|
attr_accessor :client_id
|
357
|
-
|
358
|
-
# Set to false to keep a persistent session with the
|
427
|
+
|
428
|
+
# Set to false to keep a persistent session with the server
|
359
429
|
attr_accessor :clean_session
|
360
430
|
|
361
|
-
# Period the
|
431
|
+
# Period the server should keep connection open for between pings
|
362
432
|
attr_accessor :keep_alive
|
363
|
-
|
433
|
+
|
364
434
|
# The topic name to send the Will message to
|
365
435
|
attr_accessor :will_topic
|
366
|
-
|
436
|
+
|
367
437
|
# The QoS level to send the Will message as
|
368
438
|
attr_accessor :will_qos
|
369
|
-
|
439
|
+
|
370
440
|
# Set to true to make the Will message retained
|
371
441
|
attr_accessor :will_retain
|
372
|
-
|
442
|
+
|
373
443
|
# The payload of the Will message
|
374
444
|
attr_accessor :will_payload
|
375
|
-
|
376
|
-
# The username for authenticating with the
|
445
|
+
|
446
|
+
# The username for authenticating with the server
|
377
447
|
attr_accessor :username
|
378
|
-
|
379
|
-
# The password for authenticating with the
|
448
|
+
|
449
|
+
# The password for authenticating with the server
|
380
450
|
attr_accessor :password
|
381
451
|
|
382
452
|
# Default attribute values
|
383
453
|
ATTR_DEFAULTS = {
|
384
|
-
:protocol_name => 'MQIsdp',
|
385
|
-
:protocol_version => 0x03,
|
386
454
|
:client_id => nil,
|
387
455
|
:clean_session => true,
|
388
456
|
:keep_alive => 15,
|
@@ -397,16 +465,30 @@ module MQTT
|
|
397
465
|
# Create a new Client Connect packet
|
398
466
|
def initialize(args={})
|
399
467
|
super(ATTR_DEFAULTS.merge(args))
|
468
|
+
|
469
|
+
if version == '3.1.0' or version == '3.1'
|
470
|
+
self.protocol_name ||= 'MQIsdp'
|
471
|
+
self.protocol_level ||= 0x03
|
472
|
+
elsif version == '3.1.1'
|
473
|
+
self.protocol_name ||= 'MQTT'
|
474
|
+
self.protocol_level ||= 0x04
|
475
|
+
else
|
476
|
+
raise ArgumentError.new("Unsupported protocol version: #{version}")
|
477
|
+
end
|
400
478
|
end
|
401
479
|
|
402
480
|
# Get serialisation of packet's body
|
403
481
|
def encode_body
|
404
482
|
body = ''
|
405
|
-
if @
|
406
|
-
|
483
|
+
if @version == '3.1.0'
|
484
|
+
if @client_id.nil? or @client_id.bytesize < 1
|
485
|
+
raise "Client identifier too short while serialising packet"
|
486
|
+
elsif @client_id.bytesize > 23
|
487
|
+
raise "Client identifier too long when serialising packet"
|
488
|
+
end
|
407
489
|
end
|
408
490
|
body += encode_string(@protocol_name)
|
409
|
-
body += encode_bytes(@
|
491
|
+
body += encode_bytes(@protocol_level.to_i)
|
410
492
|
|
411
493
|
if @keep_alive < 0
|
412
494
|
raise "Invalid keep-alive value: cannot be less than 0"
|
@@ -438,17 +520,14 @@ module MQTT
|
|
438
520
|
def parse_body(buffer)
|
439
521
|
super(buffer)
|
440
522
|
@protocol_name = shift_string(buffer)
|
441
|
-
@
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
end
|
448
|
-
|
449
|
-
if @protocol_version != 3
|
523
|
+
@protocol_level = shift_byte(buffer).to_i
|
524
|
+
if @protocol_name == 'MQIsdp' and @protocol_level == 3
|
525
|
+
@version = '3.1.0'
|
526
|
+
elsif @protocol_name == 'MQTT' and @protocol_level == 4
|
527
|
+
@version = '3.1.1'
|
528
|
+
else
|
450
529
|
raise ProtocolException.new(
|
451
|
-
"Unsupported protocol
|
530
|
+
"Unsupported protocol: #{@protocol_name}/#{@protocol_level}"
|
452
531
|
)
|
453
532
|
end
|
454
533
|
|
@@ -482,10 +561,26 @@ module MQTT
|
|
482
561
|
str += ", password=..." unless password.nil?
|
483
562
|
str += ">"
|
484
563
|
end
|
564
|
+
|
565
|
+
# ---- Deprecated attributes and methods ---- #
|
566
|
+
public
|
567
|
+
|
568
|
+
# @deprecated Please use {#protocol_level} instead
|
569
|
+
def protocol_version
|
570
|
+
protocol_level
|
571
|
+
end
|
572
|
+
|
573
|
+
# @deprecated Please use {#protocol_level=} instead
|
574
|
+
def protocol_version=(args)
|
575
|
+
self.protocol_level = args
|
576
|
+
end
|
485
577
|
end
|
486
578
|
|
487
579
|
# Class representing an MQTT Connect Acknowledgment Packet
|
488
580
|
class Connack < MQTT::Packet
|
581
|
+
# Session Present flag
|
582
|
+
attr_accessor :session_present
|
583
|
+
|
489
584
|
# The return code (defaults to 0 for connection accepted)
|
490
585
|
attr_accessor :return_code
|
491
586
|
|
@@ -494,9 +589,25 @@ module MQTT
|
|
494
589
|
|
495
590
|
# Create a new Client Connect packet
|
496
591
|
def initialize(args={})
|
592
|
+
# We must set flags before other attributes
|
593
|
+
@connack_flags = [false, false, false, false, false, false, false, false]
|
497
594
|
super(ATTR_DEFAULTS.merge(args))
|
498
595
|
end
|
499
596
|
|
597
|
+
# Get the Session Present flag
|
598
|
+
def session_present
|
599
|
+
@connack_flags[0]
|
600
|
+
end
|
601
|
+
|
602
|
+
# Set the Session Present flag
|
603
|
+
def session_present=(arg)
|
604
|
+
if arg.kind_of?(Integer)
|
605
|
+
@connack_flags[0] = (arg == 0x1)
|
606
|
+
else
|
607
|
+
@connack_flags[0] = arg
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
500
611
|
# Get a string message corresponding to a return code
|
501
612
|
def return_msg
|
502
613
|
case return_code
|
@@ -507,7 +618,7 @@ module MQTT
|
|
507
618
|
when 0x02
|
508
619
|
"Connection refused: client identifier rejected"
|
509
620
|
when 0x03
|
510
|
-
"Connection refused:
|
621
|
+
"Connection refused: server unavailable"
|
511
622
|
when 0x04
|
512
623
|
"Connection refused: bad user name or password"
|
513
624
|
when 0x05
|
@@ -520,15 +631,18 @@ module MQTT
|
|
520
631
|
# Get serialisation of packet's body
|
521
632
|
def encode_body
|
522
633
|
body = ''
|
523
|
-
body +=
|
524
|
-
body += encode_bytes(@return_code.to_i)
|
634
|
+
body += encode_bits(@connack_flags)
|
635
|
+
body += encode_bytes(@return_code.to_i)
|
525
636
|
return body
|
526
637
|
end
|
527
638
|
|
528
639
|
# Parse the body (variable header and payload) of a Connect Acknowledgment packet
|
529
640
|
def parse_body(buffer)
|
530
641
|
super(buffer)
|
531
|
-
|
642
|
+
@connack_flags = shift_bits(buffer)
|
643
|
+
unless @connack_flags[1,7] == [false, false, false, false, false, false, false]
|
644
|
+
raise ProtocolException.new("Invalid flags in Connack variable header")
|
645
|
+
end
|
532
646
|
@return_code = shift_byte(buffer)
|
533
647
|
unless buffer.empty?
|
534
648
|
raise ProtocolException.new("Extra bytes at end of Connect Acknowledgment packet")
|
@@ -543,26 +657,15 @@ module MQTT
|
|
543
657
|
|
544
658
|
# Class representing an MQTT Publish Acknowledgment packet
|
545
659
|
class Puback < MQTT::Packet
|
546
|
-
# Identifier for an individual publishing flow
|
547
|
-
attr_accessor :message_id
|
548
|
-
|
549
|
-
# Default attribute values
|
550
|
-
ATTR_DEFAULTS = {:message_id => 0}
|
551
|
-
|
552
|
-
# Create a new Publish Acknowledgment packet
|
553
|
-
def initialize(args={})
|
554
|
-
super(ATTR_DEFAULTS.merge(args))
|
555
|
-
end
|
556
|
-
|
557
660
|
# Get serialisation of packet's body
|
558
661
|
def encode_body
|
559
|
-
encode_short(@
|
662
|
+
encode_short(@id)
|
560
663
|
end
|
561
664
|
|
562
665
|
# Parse the body (variable header and payload) of a packet
|
563
666
|
def parse_body(buffer)
|
564
667
|
super(buffer)
|
565
|
-
@
|
668
|
+
@id = shift_short(buffer)
|
566
669
|
unless buffer.empty?
|
567
670
|
raise ProtocolException.new("Extra bytes at end of Publish Acknowledgment packet")
|
568
671
|
end
|
@@ -570,32 +673,21 @@ module MQTT
|
|
570
673
|
|
571
674
|
# Returns a human readable string, summarising the properties of the packet
|
572
675
|
def inspect
|
573
|
-
"\#<#{self.class}: 0x%2.2X>" %
|
676
|
+
"\#<#{self.class}: 0x%2.2X>" % id
|
574
677
|
end
|
575
678
|
end
|
576
679
|
|
577
680
|
# Class representing an MQTT Publish Received packet
|
578
681
|
class Pubrec < MQTT::Packet
|
579
|
-
# Identifier for an individual publishing flow
|
580
|
-
attr_accessor :message_id
|
581
|
-
|
582
|
-
# Default attribute values
|
583
|
-
ATTR_DEFAULTS = {:message_id => 0}
|
584
|
-
|
585
|
-
# Create a new Publish Recieved packet
|
586
|
-
def initialize(args={})
|
587
|
-
super(ATTR_DEFAULTS.merge(args))
|
588
|
-
end
|
589
|
-
|
590
682
|
# Get serialisation of packet's body
|
591
683
|
def encode_body
|
592
|
-
encode_short(@
|
684
|
+
encode_short(@id)
|
593
685
|
end
|
594
686
|
|
595
687
|
# Parse the body (variable header and payload) of a packet
|
596
688
|
def parse_body(buffer)
|
597
689
|
super(buffer)
|
598
|
-
@
|
690
|
+
@id = shift_short(buffer)
|
599
691
|
unless buffer.empty?
|
600
692
|
raise ProtocolException.new("Extra bytes at end of Publish Received packet")
|
601
693
|
end
|
@@ -603,65 +695,62 @@ module MQTT
|
|
603
695
|
|
604
696
|
# Returns a human readable string, summarising the properties of the packet
|
605
697
|
def inspect
|
606
|
-
"\#<#{self.class}: 0x%2.2X>" %
|
698
|
+
"\#<#{self.class}: 0x%2.2X>" % id
|
607
699
|
end
|
608
700
|
end
|
609
701
|
|
610
702
|
# Class representing an MQTT Publish Release packet
|
611
703
|
class Pubrel < MQTT::Packet
|
612
|
-
# Identifier for an individual publishing flow
|
613
|
-
attr_accessor :message_id
|
614
704
|
|
615
705
|
# Default attribute values
|
616
|
-
ATTR_DEFAULTS = {
|
706
|
+
ATTR_DEFAULTS = {
|
707
|
+
:flags => [false, true, false, false],
|
708
|
+
}
|
617
709
|
|
618
|
-
# Create a new
|
710
|
+
# Create a new Pubrel packet
|
619
711
|
def initialize(args={})
|
620
712
|
super(ATTR_DEFAULTS.merge(args))
|
621
713
|
end
|
622
714
|
|
623
715
|
# Get serialisation of packet's body
|
624
716
|
def encode_body
|
625
|
-
encode_short(@
|
717
|
+
encode_short(@id)
|
626
718
|
end
|
627
719
|
|
628
720
|
# Parse the body (variable header and payload) of a packet
|
629
721
|
def parse_body(buffer)
|
630
722
|
super(buffer)
|
631
|
-
@
|
723
|
+
@id = shift_short(buffer)
|
632
724
|
unless buffer.empty?
|
633
725
|
raise ProtocolException.new("Extra bytes at end of Publish Release packet")
|
634
726
|
end
|
635
727
|
end
|
636
728
|
|
729
|
+
# Check that fixed header flags are valid for this packet type
|
730
|
+
# @private
|
731
|
+
def validate_flags
|
732
|
+
if @flags != [false, true, false, false]
|
733
|
+
raise ProtocolException.new("Invalid flags in PUBREL packet header")
|
734
|
+
end
|
735
|
+
end
|
736
|
+
|
637
737
|
# Returns a human readable string, summarising the properties of the packet
|
638
738
|
def inspect
|
639
|
-
"\#<#{self.class}: 0x%2.2X>" %
|
739
|
+
"\#<#{self.class}: 0x%2.2X>" % id
|
640
740
|
end
|
641
741
|
end
|
642
742
|
|
643
743
|
# Class representing an MQTT Publish Complete packet
|
644
744
|
class Pubcomp < MQTT::Packet
|
645
|
-
# Identifier for an individual publishing flow
|
646
|
-
attr_accessor :message_id
|
647
|
-
|
648
|
-
# Default attribute values
|
649
|
-
ATTR_DEFAULTS = {:message_id => 0}
|
650
|
-
|
651
|
-
# Create a new Publish Complete packet
|
652
|
-
def initialize(args={})
|
653
|
-
super(ATTR_DEFAULTS.merge(args))
|
654
|
-
end
|
655
|
-
|
656
745
|
# Get serialisation of packet's body
|
657
746
|
def encode_body
|
658
|
-
encode_short(@
|
747
|
+
encode_short(@id)
|
659
748
|
end
|
660
749
|
|
661
750
|
# Parse the body (variable header and payload) of a packet
|
662
751
|
def parse_body(buffer)
|
663
752
|
super(buffer)
|
664
|
-
@
|
753
|
+
@id = shift_short(buffer)
|
665
754
|
unless buffer.empty?
|
666
755
|
raise ProtocolException.new("Extra bytes at end of Publish Complete packet")
|
667
756
|
end
|
@@ -669,29 +758,27 @@ module MQTT
|
|
669
758
|
|
670
759
|
# Returns a human readable string, summarising the properties of the packet
|
671
760
|
def inspect
|
672
|
-
"\#<#{self.class}: 0x%2.2X>" %
|
761
|
+
"\#<#{self.class}: 0x%2.2X>" % id
|
673
762
|
end
|
674
763
|
end
|
675
764
|
|
676
765
|
# Class representing an MQTT Client Subscribe packet
|
677
766
|
class Subscribe < MQTT::Packet
|
678
|
-
#
|
679
|
-
attr_accessor :
|
680
|
-
|
681
|
-
# One or more topic names to subscribe to
|
682
|
-
attr_reader :topics
|
767
|
+
# One or more topic filters to subscribe to
|
768
|
+
attr_accessor :topics
|
683
769
|
|
684
770
|
# Default attribute values
|
685
|
-
ATTR_DEFAULTS = {
|
771
|
+
ATTR_DEFAULTS = {
|
772
|
+
:topics => [],
|
773
|
+
:flags => [false, true, false, false],
|
774
|
+
}
|
686
775
|
|
687
776
|
# Create a new Subscribe packet
|
688
777
|
def initialize(args={})
|
689
778
|
super(ATTR_DEFAULTS.merge(args))
|
690
|
-
@topics ||= []
|
691
|
-
@qos = 1 # Force a QOS of 1
|
692
779
|
end
|
693
780
|
|
694
|
-
# Set one or more
|
781
|
+
# Set one or more topic filters for the Subscribe packet
|
695
782
|
# The topics parameter should be one of the following:
|
696
783
|
# * String: subscribe to one topic with QOS 0
|
697
784
|
# * Array: subscribe to multiple topics with QOS 0
|
@@ -738,7 +825,7 @@ module MQTT
|
|
738
825
|
if @topics.empty?
|
739
826
|
raise "no topics given when serialising packet"
|
740
827
|
end
|
741
|
-
body = encode_short(@
|
828
|
+
body = encode_short(@id)
|
742
829
|
topics.each do |item|
|
743
830
|
body += encode_string(item[0])
|
744
831
|
body += encode_bytes(item[1])
|
@@ -749,7 +836,7 @@ module MQTT
|
|
749
836
|
# Parse the body (variable header and payload) of a packet
|
750
837
|
def parse_body(buffer)
|
751
838
|
super(buffer)
|
752
|
-
@
|
839
|
+
@id = shift_short(buffer)
|
753
840
|
@topics = []
|
754
841
|
while(buffer.bytesize>0)
|
755
842
|
topic_name = shift_string(buffer)
|
@@ -758,10 +845,18 @@ module MQTT
|
|
758
845
|
end
|
759
846
|
end
|
760
847
|
|
848
|
+
# Check that fixed header flags are valid for this packet type
|
849
|
+
# @private
|
850
|
+
def validate_flags
|
851
|
+
if @flags != [false, true, false, false]
|
852
|
+
raise ProtocolException.new("Invalid flags in SUBSCRIBE packet header")
|
853
|
+
end
|
854
|
+
end
|
855
|
+
|
761
856
|
# Returns a human readable string, summarising the properties of the packet
|
762
857
|
def inspect
|
763
858
|
_str = "\#<#{self.class}: 0x%2.2X, %s>" % [
|
764
|
-
|
859
|
+
id,
|
765
860
|
topics.map {|t| "'#{t[0]}':#{t[1]}"}.join(', ')
|
766
861
|
]
|
767
862
|
end
|
@@ -769,77 +864,86 @@ module MQTT
|
|
769
864
|
|
770
865
|
# Class representing an MQTT Subscribe Acknowledgment packet
|
771
866
|
class Suback < MQTT::Packet
|
772
|
-
#
|
773
|
-
attr_accessor :
|
774
|
-
|
775
|
-
# The QoS level that was granted for the subscribe request
|
776
|
-
attr_reader :granted_qos
|
867
|
+
# An array of return codes, ordered by the topics that were subscribed to
|
868
|
+
attr_accessor :return_codes
|
777
869
|
|
778
870
|
# Default attribute values
|
779
|
-
ATTR_DEFAULTS = {
|
871
|
+
ATTR_DEFAULTS = {
|
872
|
+
:return_codes => [],
|
873
|
+
}
|
780
874
|
|
781
875
|
# Create a new Subscribe Acknowledgment packet
|
782
876
|
def initialize(args={})
|
783
877
|
super(ATTR_DEFAULTS.merge(args))
|
784
|
-
@granted_qos ||= []
|
785
878
|
end
|
786
879
|
|
787
880
|
# Set the granted QOS value for each of the topics that were subscribed to
|
788
881
|
# Can either be an integer or an array or integers.
|
789
|
-
def
|
882
|
+
def return_codes=(value)
|
790
883
|
if value.is_a?(Array)
|
791
|
-
@
|
884
|
+
@return_codes = value
|
792
885
|
elsif value.is_a?(Integer)
|
793
|
-
@
|
886
|
+
@return_codes = [value]
|
794
887
|
else
|
795
|
-
raise "
|
888
|
+
raise "return_codes should be an integer or an array of return codes"
|
796
889
|
end
|
797
890
|
end
|
798
891
|
|
799
892
|
# Get serialisation of packet's body
|
800
893
|
def encode_body
|
801
|
-
if @
|
894
|
+
if @return_codes.empty?
|
802
895
|
raise "no granted QOS given when serialising packet"
|
803
896
|
end
|
804
|
-
body = encode_short(@
|
805
|
-
|
897
|
+
body = encode_short(@id)
|
898
|
+
return_codes.each { |qos| body += encode_bytes(qos) }
|
806
899
|
return body
|
807
900
|
end
|
808
901
|
|
809
902
|
# Parse the body (variable header and payload) of a packet
|
810
903
|
def parse_body(buffer)
|
811
904
|
super(buffer)
|
812
|
-
@
|
905
|
+
@id = shift_short(buffer)
|
813
906
|
while(buffer.bytesize>0)
|
814
|
-
@
|
907
|
+
@return_codes << shift_byte(buffer)
|
815
908
|
end
|
816
909
|
end
|
817
910
|
|
818
911
|
# Returns a human readable string, summarising the properties of the packet
|
819
912
|
def inspect
|
820
|
-
"\#<#{self.class}: 0x%2.2X,
|
913
|
+
"\#<#{self.class}: 0x%2.2X, rc=%s>" % [id, return_codes.map{|rc| "0x%2.2X" % rc}.join(',')]
|
914
|
+
end
|
915
|
+
|
916
|
+
# ---- Deprecated attributes and methods ---- #
|
917
|
+
public
|
918
|
+
|
919
|
+
# @deprecated Please use {#return_codes} instead
|
920
|
+
def granted_qos
|
921
|
+
return_codes
|
922
|
+
end
|
923
|
+
|
924
|
+
# @deprecated Please use {#return_codes=} instead
|
925
|
+
def granted_qos=(args)
|
926
|
+
self.return_codes = args
|
821
927
|
end
|
822
928
|
end
|
823
929
|
|
824
930
|
# Class representing an MQTT Client Unsubscribe packet
|
825
931
|
class Unsubscribe < MQTT::Packet
|
826
|
-
# One or more
|
827
|
-
|
828
|
-
|
829
|
-
# Identifier to tie the Unsubscribe request to the Unsuback response
|
830
|
-
attr_accessor :message_id
|
932
|
+
# One or more topic paths to unsubscribe from
|
933
|
+
attr_accessor :topics
|
831
934
|
|
832
935
|
# Default attribute values
|
833
|
-
ATTR_DEFAULTS = {
|
936
|
+
ATTR_DEFAULTS = {
|
937
|
+
:topics => [],
|
938
|
+
:flags => [false, true, false, false],
|
939
|
+
}
|
834
940
|
|
835
941
|
# Create a new Unsubscribe packet
|
836
942
|
def initialize(args={})
|
837
943
|
super(ATTR_DEFAULTS.merge(args))
|
838
|
-
@topics ||= []
|
839
|
-
@qos = 1 # Force a QOS of 1
|
840
944
|
end
|
841
945
|
|
842
|
-
# Set one or more
|
946
|
+
# Set one or more topic paths to unsubscribe from
|
843
947
|
def topics=(value)
|
844
948
|
if value.is_a?(Array)
|
845
949
|
@topics = value
|
@@ -853,7 +957,7 @@ module MQTT
|
|
853
957
|
if @topics.empty?
|
854
958
|
raise "no topics given when serialising packet"
|
855
959
|
end
|
856
|
-
body = encode_short(@
|
960
|
+
body = encode_short(@id)
|
857
961
|
topics.each { |topic| body += encode_string(topic) }
|
858
962
|
return body
|
859
963
|
end
|
@@ -861,16 +965,24 @@ module MQTT
|
|
861
965
|
# Parse the body (variable header and payload) of a packet
|
862
966
|
def parse_body(buffer)
|
863
967
|
super(buffer)
|
864
|
-
@
|
968
|
+
@id = shift_short(buffer)
|
865
969
|
while(buffer.bytesize>0)
|
866
970
|
@topics << shift_string(buffer)
|
867
971
|
end
|
868
972
|
end
|
869
973
|
|
974
|
+
# Check that fixed header flags are valid for this packet type
|
975
|
+
# @private
|
976
|
+
def validate_flags
|
977
|
+
if @flags != [false, true, false, false]
|
978
|
+
raise ProtocolException.new("Invalid flags in UNSUBSCRIBE packet header")
|
979
|
+
end
|
980
|
+
end
|
981
|
+
|
870
982
|
# Returns a human readable string, summarising the properties of the packet
|
871
983
|
def inspect
|
872
984
|
"\#<#{self.class}: 0x%2.2X, %s>" % [
|
873
|
-
|
985
|
+
id,
|
874
986
|
topics.map {|t| "'#{t}'"}.join(', ')
|
875
987
|
]
|
876
988
|
end
|
@@ -878,26 +990,20 @@ module MQTT
|
|
878
990
|
|
879
991
|
# Class representing an MQTT Unsubscribe Acknowledgment packet
|
880
992
|
class Unsuback < MQTT::Packet
|
881
|
-
# Identifier to tie the Unsubscribe request to the Unsuback response
|
882
|
-
attr_accessor :message_id
|
883
|
-
|
884
|
-
# Default attribute values
|
885
|
-
ATTR_DEFAULTS = {:message_id => 0}
|
886
|
-
|
887
993
|
# Create a new Unsubscribe Acknowledgment packet
|
888
994
|
def initialize(args={})
|
889
|
-
super(
|
995
|
+
super(args)
|
890
996
|
end
|
891
997
|
|
892
998
|
# Get serialisation of packet's body
|
893
999
|
def encode_body
|
894
|
-
encode_short(@
|
1000
|
+
encode_short(@id)
|
895
1001
|
end
|
896
1002
|
|
897
1003
|
# Parse the body (variable header and payload) of a packet
|
898
1004
|
def parse_body(buffer)
|
899
1005
|
super(buffer)
|
900
|
-
@
|
1006
|
+
@id = shift_short(buffer)
|
901
1007
|
unless buffer.empty?
|
902
1008
|
raise ProtocolException.new("Extra bytes at end of Unsubscribe Acknowledgment packet")
|
903
1009
|
end
|
@@ -905,7 +1011,7 @@ module MQTT
|
|
905
1011
|
|
906
1012
|
# Returns a human readable string, summarising the properties of the packet
|
907
1013
|
def inspect
|
908
|
-
"\#<#{self.class}: 0x%2.2X>" %
|
1014
|
+
"\#<#{self.class}: 0x%2.2X>" % id
|
909
1015
|
end
|
910
1016
|
end
|
911
1017
|
|
@@ -956,6 +1062,20 @@ module MQTT
|
|
956
1062
|
end
|
957
1063
|
end
|
958
1064
|
end
|
1065
|
+
|
1066
|
+
|
1067
|
+
# ---- Deprecated attributes and methods ---- #
|
1068
|
+
public
|
1069
|
+
|
1070
|
+
# @deprecated Please use {#id} instead
|
1071
|
+
def message_id
|
1072
|
+
id
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
# @deprecated Please use {#id=} instead
|
1076
|
+
def message_id=(args)
|
1077
|
+
self.id = args
|
1078
|
+
end
|
959
1079
|
end
|
960
1080
|
|
961
1081
|
|