mqtt 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # Duplicate delivery flag
9
- attr_reader :duplicate
8
+ # The version number of the MQTT protocol to use (default 3.1.0)
9
+ attr_accessor :version
10
10
 
11
- # Retain flag
12
- attr_reader :retain
11
+ # Identifier to link related control packets together
12
+ attr_accessor :id
13
13
 
14
- # Quality of Service level (0, 1, 2)
15
- attr_reader :qos
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
- :duplicate => false,
23
- :qos => 0,
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
- send("#{k}=", v)
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
- # Set the dup flag (true/false)
136
- def duplicate=(arg)
137
- if arg.kind_of?(Integer)
138
- @duplicate = (arg != 0)
139
- else
140
- @duplicate = arg
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 retain flag (true/false)
145
- def retain=(arg)
146
- if arg.kind_of?(Integer)
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
- ((duplicate ? 0x1 : 0x0) << 3) |
187
- ((qos.to_i & 0x03) << 1) |
188
- (retain ? 0x1 : 0x0)
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
- :topic => nil,
296
- :message_id => 0,
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(@message_id) unless qos == 0
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
- @message_id = shift_short(buffer) unless qos == 0
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#{message_id}, " +
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 (defaults to MQIsdp)
419
+ # The name of the protocol
350
420
  attr_accessor :protocol_name
351
421
 
352
- # The version number of the protocol (defaults to 3)
353
- attr_accessor :protocol_version
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 broker
427
+
428
+ # Set to false to keep a persistent session with the server
359
429
  attr_accessor :clean_session
360
430
 
361
- # Period the broker should keep connection open for between pings
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 broker
445
+
446
+ # The username for authenticating with the server
377
447
  attr_accessor :username
378
-
379
- # The password for authenticating with the broker
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 @client_id.nil? or @client_id.bytesize < 1 or @client_id.bytesize > 23
406
- raise "Invalid client identifier when serialising packet"
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(@protocol_version.to_i)
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
- @protocol_version = shift_byte(buffer).to_i
442
-
443
- if @protocol_name != 'MQIsdp'
444
- raise ProtocolException.new(
445
- "Unsupported protocol name: #{@protocol_name}"
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 version: #{@protocol_version}"
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: broker unavailable"
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 += encode_bytes(0) # Unused
524
- body += encode_bytes(@return_code.to_i) # Return Code
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
- _unused = shift_byte(buffer)
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(@message_id)
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
- @message_id = shift_short(buffer)
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>" % message_id
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(@message_id)
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
- @message_id = shift_short(buffer)
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>" % message_id
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 = {:message_id => 0}
706
+ ATTR_DEFAULTS = {
707
+ :flags => [false, true, false, false],
708
+ }
617
709
 
618
- # Create a new Publish Release packet
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(@message_id)
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
- @message_id = shift_short(buffer)
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>" % message_id
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(@message_id)
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
- @message_id = shift_short(buffer)
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>" % message_id
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
- # Identifier for an individual publishing flow
679
- attr_accessor :message_id
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 = {:message_id => 0}
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 topics for the Subscrible packet
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(@message_id)
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
- @message_id = shift_short(buffer)
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
- message_id,
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
- # Identifier to tie the Subscribe request to the Suback response
773
- attr_accessor :message_id
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 = {:message_id => 0}
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 granted_qos=(value)
882
+ def return_codes=(value)
790
883
  if value.is_a?(Array)
791
- @granted_qos = value
884
+ @return_codes = value
792
885
  elsif value.is_a?(Integer)
793
- @granted_qos = [value]
886
+ @return_codes = [value]
794
887
  else
795
- raise "granted QOS should be an integer or an array of QOS levels"
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 @granted_qos.empty?
894
+ if @return_codes.empty?
802
895
  raise "no granted QOS given when serialising packet"
803
896
  end
804
- body = encode_short(@message_id)
805
- granted_qos.each { |qos| body += encode_bytes(qos) }
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
- @message_id = shift_short(buffer)
905
+ @id = shift_short(buffer)
813
906
  while(buffer.bytesize>0)
814
- @granted_qos << shift_byte(buffer)
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, qos=%s>" % [message_id, granted_qos.join(',')]
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 topics to unsubscribe from
827
- attr_reader :topics
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 = {:message_id => 0}
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 topics to unsubscribe from
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(@message_id)
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
- @message_id = shift_short(buffer)
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
- message_id,
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(ATTR_DEFAULTS.merge(args))
995
+ super(args)
890
996
  end
891
997
 
892
998
  # Get serialisation of packet's body
893
999
  def encode_body
894
- encode_short(@message_id)
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
- @message_id = shift_short(buffer)
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>" % message_id
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