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.
@@ -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