mqtt 0.2.0 → 0.3.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.
- 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
|
|