paho-mqtt 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ # encoding: BINARY
2
+
3
+ module PahoMqtt
4
+ module Packet
5
+ class Unsuback < PahoMqtt::Packet::Base
6
+ # Create a new Unsubscribe Acknowledgment packet
7
+ def initialize(args={})
8
+ super(args)
9
+ end
10
+
11
+ # Get serialisation of packet's body
12
+ def encode_body
13
+ encode_short(@id)
14
+ end
15
+
16
+ # Parse the body (variable header and payload) of a packet
17
+ def parse_body(buffer)
18
+ super(buffer)
19
+ @id = shift_short(buffer)
20
+ unless buffer.empty?
21
+ raise "Extra bytes at end of Unsubscribe Acknowledgment packet"
22
+ end
23
+ end
24
+
25
+ # Returns a human readable string, summarising the properties of the packet
26
+ def inspect
27
+ "\#<#{self.class}: 0x%2.2X>" % id
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: BINARY
2
+
3
+ module PahoMqtt
4
+ module Packet
5
+ class Unsubscribe < PahoMqtt::Packet::Base
6
+ # One or more topic paths to unsubscribe from
7
+ attr_accessor :topics
8
+
9
+ # Default attribute values
10
+ ATTR_DEFAULTS = {
11
+ :topics => [],
12
+ :flags => [false, true, false, false],
13
+ }
14
+
15
+ # Create a new Unsubscribe packet
16
+ def initialize(args={})
17
+ super(ATTR_DEFAULTS.merge(args))
18
+ end
19
+
20
+ # Set one or more topic paths to unsubscribe from
21
+ def topics=(value)
22
+ if value.is_a?(Array)
23
+ @topics = value
24
+ else
25
+ @topics = [value]
26
+ end
27
+ end
28
+
29
+ # Get serialisation of packet's body
30
+ def encode_body
31
+ if @topics.empty?
32
+ raise "no topics given when serialising packet"
33
+ end
34
+ body = encode_short(@id)
35
+ topics.each { |topic| body += encode_string(topic) }
36
+ return body
37
+ end
38
+
39
+ # Parse the body (variable header and payload) of a packet
40
+ def parse_body(buffer)
41
+ super(buffer)
42
+ @id = shift_short(buffer)
43
+ while(buffer.bytesize>0)
44
+ @topics << shift_string(buffer)
45
+ end
46
+ end
47
+
48
+ # Check that fixed header flags are valid for this packet type
49
+ # @private
50
+ def validate_flags
51
+ if @flags != [false, true, false, false]
52
+ raise "Invalid flags in UNSUBSCRIBE packet header"
53
+ end
54
+ end
55
+
56
+ # Returns a human readable string, summarising the properties of the packet
57
+ def inspect
58
+ "\#<#{self.class}: 0x%2.2X, %s>" % [
59
+ id,
60
+ topics.map {|t| "'#{t}'"}.join(', ')
61
+ ]
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,3 +1,3 @@
1
1
  module PahoMqtt
2
- VERSION = "0.0.2"
2
+ VERSION = "1.0.0"
3
3
  end
data/paho-mqtt.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'paho.mqtt/version'
4
+ require 'paho_mqtt/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "paho-mqtt"
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
 
12
12
  spec.summary = %q{A simple mqtt client gem}
13
13
  spec.description = %q{A simple mqtt client gem}
14
- spec.homepage = "http://ruby-dev.jp"
14
+ spec.homepage = "https://github.com/RubyDevInc/paho.mqtt.ruby"
15
15
  spec.license = "MIT"
16
16
 
17
17
  # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
@@ -0,0 +1,16 @@
1
+ require 'paho-mqtt'
2
+ require 'logger'
3
+
4
+ file = File.open('paho_writting.log', "a+")
5
+ log = Logger.new(file)
6
+ log.level = Logger::DEBUG
7
+
8
+ client = PahoMqtt::Client.new({logger: log})
9
+
10
+ client.connect('localhost', 1883, client.keep_alive, true, true)
11
+
12
+ loop do
13
+ client.publish("topic_test", "Hello, Are you there?", false, 1)
14
+ client.loop_write
15
+ sleep 1
16
+ end
@@ -0,0 +1,34 @@
1
+ require 'paho-mqtt'
2
+ require 'logger'
3
+
4
+ file = File.open('paho.log', "a+")
5
+ log = Logger.new(file)
6
+ log.level = Logger::DEBUG
7
+
8
+ client = PahoMqtt::Client.new({logger: log})
9
+
10
+ client.on_message do |pck|
11
+ puts "New Message: #{pck.topic}\n>>> #{pck.payload}"
12
+ end
13
+
14
+ wait_suback = true
15
+ client.on_suback do |pck|
16
+ wait_suback = false
17
+ end
18
+
19
+ client.connect('localhost', 1883, client.keep_alive, true, true)
20
+
21
+ Thread.new do
22
+ while wait_suback do
23
+ client.loop_read
24
+ sleep 0.001
25
+ end
26
+ end
27
+
28
+ client.subscribe(["topic_test", 2])
29
+ client.loop_write
30
+
31
+ loop do
32
+ client.loop_read
33
+ sleep 0.01
34
+ end
@@ -0,0 +1,49 @@
1
+ require 'paho-mqtt'
2
+
3
+ ### Create a simple client with default attributes
4
+ client = PahoMqtt::Client.new
5
+
6
+ ### Register a callback on message event to display messages
7
+ message_counter = 0
8
+ client.on_message do |message|
9
+ puts "Message recieved on topic: #{message.topic}\n>>> #{message.payload}"
10
+ message_counter += 1
11
+ end
12
+
13
+ ### Register a callback on suback to assert the subcription
14
+ waiting_suback = true
15
+ client.on_suback do
16
+ waiting_suback = false
17
+ puts "Subscribed"
18
+ end
19
+
20
+ ### Register a callback for puback event when receiving a puback
21
+ waiting_puback = true
22
+ client.on_puback do
23
+ waiting_puback = false
24
+ puts "Message Acknowledged"
25
+ end
26
+
27
+ ### Connect to the eclipse test server on port 1883 (Unencrypted mode)
28
+ client.connect('iot.eclipse.org', 1883)
29
+
30
+ ### Subscribe to a topic
31
+ client.subscribe(['/paho/ruby/test', 2])
32
+
33
+ ### Waiting for the suback answer and excute the previously set on_suback callback
34
+ while waiting_suback do
35
+ sleep 0.001
36
+ end
37
+
38
+ ### Publlish a message on the topic "/paho/ruby/test" with "retain == false" and "qos == 1"
39
+ client.publish("/paho/ruby/test", "Hello there!", false, 1)
40
+
41
+ while waiting_puback do
42
+ sleep 0.001
43
+ end
44
+
45
+ ### Waiting to assert that the message is displayed by on_message callback
46
+ sleep 1
47
+
48
+ ### Calling an explicit disconnect
49
+ client.disconnect
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paho-mqtt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pierre Goudet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-01-06 00:00:00.000000000 Z
11
+ date: 2017-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -70,14 +70,30 @@ files:
70
70
  - bin/console
71
71
  - bin/setup
72
72
  - lib/paho-mqtt.rb
73
- - lib/paho.mqtt/packet_manager.rb
74
- - lib/paho.mqtt/paho_client.rb
75
- - lib/paho.mqtt/version.rb
76
- - paho-mqtt-0.0.1.gem
73
+ - lib/paho_mqtt/client.rb
74
+ - lib/paho_mqtt/packet.rb
75
+ - lib/paho_mqtt/packet/base.rb
76
+ - lib/paho_mqtt/packet/connack.rb
77
+ - lib/paho_mqtt/packet/connect.rb
78
+ - lib/paho_mqtt/packet/disconnect.rb
79
+ - lib/paho_mqtt/packet/pingreq.rb
80
+ - lib/paho_mqtt/packet/pingresp.rb
81
+ - lib/paho_mqtt/packet/puback.rb
82
+ - lib/paho_mqtt/packet/pubcomp.rb
83
+ - lib/paho_mqtt/packet/publish.rb
84
+ - lib/paho_mqtt/packet/pubrec.rb
85
+ - lib/paho_mqtt/packet/pubrel.rb
86
+ - lib/paho_mqtt/packet/suback.rb
87
+ - lib/paho_mqtt/packet/subscribe.rb
88
+ - lib/paho_mqtt/packet/unsuback.rb
89
+ - lib/paho_mqtt/packet/unsubscribe.rb
90
+ - lib/paho_mqtt/version.rb
77
91
  - paho-mqtt.gemspec
78
- - samples/test_aws.rb
92
+ - samples/client_blocking(writing).rb
93
+ - samples/client_blocking.rb
94
+ - samples/getting_started.rb
79
95
  - samples/test_client.rb
80
- homepage: http://ruby-dev.jp
96
+ homepage: https://github.com/RubyDevInc/paho.mqtt.ruby
81
97
  licenses:
82
98
  - MIT
83
99
  metadata: {}
@@ -1,1056 +0,0 @@
1
- # encoding: BINARY
2
-
3
- module PahoMqtt
4
- # Class representing a MQTT Packet
5
- # Performs binary encoding and decoding of headers
6
- class Packet
7
- # The version number of the MQTT protocol to use (default 3.1.0)
8
- attr_accessor :version
9
-
10
- # Identifier to link related control packets together
11
- attr_accessor :id
12
-
13
- # Array of 4 bits in the fixed header
14
- attr_accessor :flags
15
-
16
- # The length of the parsed packet body
17
- attr_reader :body_length
18
-
19
- # Default attribute values
20
- ATTR_DEFAULTS = {
21
- :version => '3.1.0',
22
- :id => 0,
23
- :body_length => nil
24
- }
25
-
26
- # Read in a packet from a socket
27
- def self.read(socket)
28
- # Read in the packet header and create a new packet object
29
- packet = create_from_header(
30
- read_byte(socket)
31
- )
32
- unless packet.nil?
33
- packet.validate_flags
34
-
35
- # Read in the packet length
36
- multiplier = 1
37
- body_length = 0
38
- pos = 1
39
- begin
40
- digit = read_byte(socket)
41
- body_length += ((digit & 0x7F) * multiplier)
42
- multiplier *= 0x80
43
- pos += 1
44
- end while ((digit & 0x80) != 0x00) and pos <= 4
45
-
46
- # Store the expected body length in the packet
47
- packet.instance_variable_set('@body_length', body_length)
48
-
49
- # Read in the packet body
50
- packet.parse_body( socket.read(body_length) )
51
- end
52
- return packet
53
- end
54
-
55
- # Parse buffer into new packet object
56
- def self.parse(buffer)
57
- packet = parse_header(buffer)
58
- packet.parse_body(buffer)
59
- return packet
60
- end
61
-
62
- # Parse the header and create a new packet object of the correct type
63
- # The header is removed from the buffer passed into this function
64
- def self.parse_header(buffer)
65
- # Check that the packet is a long as the minimum packet size
66
- if buffer.bytesize < 2
67
- raise "Invalid packet: less than 2 bytes long"
68
- end
69
-
70
- # Create a new packet object
71
- bytes = buffer.unpack("C5")
72
- packet = create_from_header(bytes.first)
73
- packet.validate_flags
74
-
75
- # Parse the packet length
76
- body_length = 0
77
- multiplier = 1
78
- pos = 1
79
- begin
80
- if buffer.bytesize <= pos
81
- raise "The packet length header is incomplete"
82
- end
83
- digit = bytes[pos]
84
- body_length += ((digit & 0x7F) * multiplier)
85
- multiplier *= 0x80
86
- pos += 1
87
- end while ((digit & 0x80) != 0x00) and pos <= 4
88
-
89
- # Store the expected body length in the packet
90
- packet.instance_variable_set('@body_length', body_length)
91
-
92
- # Delete the fixed header from the raw packet passed in
93
- buffer.slice!(0...pos)
94
-
95
- return packet
96
- end
97
-
98
- # Create a new packet object from the first byte of a MQTT packet
99
- def self.create_from_header(byte)
100
- unless byte.nil?
101
- # Work out the class
102
- type_id = ((byte & 0xF0) >> 4)
103
- packet_class = PahoMqtt::PACKET_TYPES[type_id]
104
- if packet_class.nil?
105
- raise "Invalid packet type identifier: #{type_id}"
106
- end
107
-
108
- # Convert the last 4 bits of byte into array of true/false
109
- flags = (0..3).map { |i| byte & (2 ** i) != 0 }
110
-
111
- # Create a new packet object
112
- packet_class.new(:flags => flags)
113
- end
114
- end
115
-
116
- # Create a new empty packet
117
- def initialize(args={})
118
- # We must set flags before the other values
119
- @flags = [false, false, false, false]
120
- update_attributes(ATTR_DEFAULTS.merge(args))
121
- end
122
-
123
- # Set packet attributes from a hash of attribute names and values
124
- def update_attributes(attr={})
125
- attr.each_pair do |k,v|
126
- if v.is_a?(Array) or v.is_a?(Hash)
127
- send("#{k}=", v.dup)
128
- else
129
- send("#{k}=", v)
130
- end
131
- end
132
- end
133
-
134
- # Get the identifer for this packet type
135
- def type_id
136
- index = PahoMqtt::PACKET_TYPES.index(self.class)
137
- if index.nil?
138
- raise "Invalid packet type: #{self.class}"
139
- end
140
- return index
141
- end
142
-
143
- # Get the name of the packet type as a string in capitals
144
- # (like the MQTT specification uses)
145
- #
146
- # Example: CONNACK
147
- def type_name
148
- self.class.name.split('::').last.upcase
149
- end
150
-
151
- # Set the protocol version number
152
- def version=(arg)
153
- @version = arg.to_s
154
- end
155
-
156
- # Set the length of the packet body
157
- def body_length=(arg)
158
- @body_length = arg.to_i
159
- end
160
-
161
- # Parse the body (variable header and payload) of a packet
162
- def parse_body(buffer)
163
- if buffer.bytesize != body_length
164
- raise "Failed to parse packet - input buffer (#{buffer.bytesize}) is not the same as the body length header (#{body_length})"
165
- end
166
- end
167
-
168
- # Get serialisation of packet's body (variable header and payload)
169
- def encode_body
170
- '' # No body by default
171
- end
172
-
173
- # Serialise the packet
174
- def to_s
175
- # Encode the fixed header
176
- header = [
177
- ((type_id.to_i & 0x0F) << 4) |
178
- (flags[3] ? 0x8 : 0x0) |
179
- (flags[2] ? 0x4 : 0x0) |
180
- (flags[1] ? 0x2 : 0x0) |
181
- (flags[0] ? 0x1 : 0x0)
182
- ]
183
-
184
- # Get the packet's variable header and payload
185
- body = self.encode_body
186
-
187
- # Check that that packet isn't too big
188
- body_length = body.bytesize
189
- if body_length > 268435455
190
- raise "Error serialising packet: body is more than 256MB"
191
- end
192
-
193
- # Build up the body length field bytes
194
- begin
195
- digit = (body_length % 128)
196
- body_length = (body_length / 128)
197
- # if there are more digits to encode, set the top bit of this digit
198
- digit |= 0x80 if (body_length > 0)
199
- header.push(digit)
200
- end while (body_length > 0)
201
-
202
- # Convert header to binary and add on body
203
- header.pack('C*') + body
204
- end
205
-
206
- # Check that fixed header flags are valid for types that don't use the flags
207
- # @private
208
- def validate_flags
209
- if flags != [false, false, false, false]
210
- raise "Invalid flags in #{type_name} packet header"
211
- end
212
- end
213
-
214
- # Returns a human readable string
215
- def inspect
216
- "\#<#{self.class}>"
217
- end
218
-
219
- protected
220
-
221
- # Encode an array of bytes and return them
222
- def encode_bytes(*bytes)
223
- bytes.pack('C*')
224
- end
225
-
226
- # Encode an array of bits and return them
227
- def encode_bits(bits)
228
- [bits.map{|b| b ? '1' : '0'}.join].pack('b*')
229
- end
230
-
231
- # Encode a 16-bit unsigned integer and return it
232
- def encode_short(val)
233
- [val.to_i].pack('n')
234
- end
235
-
236
- # Encode a UTF-8 string and return it
237
- # (preceded by the length of the string)
238
- def encode_string(str)
239
- str = str.to_s.encode('UTF-8')
240
-
241
- # Force to binary, when assembling the packet
242
- str.force_encoding('ASCII-8BIT')
243
- encode_short(str.bytesize) + str
244
- end
245
-
246
- # Remove a 16-bit unsigned integer from the front of buffer
247
- def shift_short(buffer)
248
- bytes = buffer.slice!(0..1)
249
- bytes.unpack('n').first
250
- end
251
-
252
- # Remove one byte from the front of the string
253
- def shift_byte(buffer)
254
- buffer.slice!(0...1).unpack('C').first
255
- end
256
-
257
- # Remove 8 bits from the front of buffer
258
- def shift_bits(buffer)
259
- buffer.slice!(0...1).unpack('b8').first.split('').map {|b| b == '1'}
260
- end
261
-
262
- # Remove n bytes from the front of buffer
263
- def shift_data(buffer,bytes)
264
- buffer.slice!(0...bytes)
265
- end
266
-
267
- # Remove string from the front of buffer
268
- def shift_string(buffer)
269
- len = shift_short(buffer)
270
- str = shift_data(buffer,len)
271
- # Strings in MQTT v3.1 are all UTF-8
272
- str.force_encoding('UTF-8')
273
- end
274
-
275
-
276
- private
277
-
278
- # Read and unpack a single byte from a socket
279
- def self.read_byte(socket)
280
- byte = socket.read(1)
281
- unless byte.nil?
282
- byte.unpack('C').first
283
- else
284
- nil
285
- end
286
- end
287
-
288
- ## PACKET SUBCLASSES ##
289
-
290
-
291
- # Class representing an MQTT Publish message
292
- class Publish < PahoMqtt::Packet
293
-
294
- # Duplicate delivery flag
295
- attr_accessor :duplicate
296
-
297
- # Retain flag
298
- attr_accessor :retain
299
-
300
- # Quality of Service level (0, 1, 2)
301
- attr_accessor :qos
302
-
303
- # The topic name to publish to
304
- attr_accessor :topic
305
-
306
- # The data to be published
307
- attr_accessor :payload
308
-
309
- # Default attribute values
310
- ATTR_DEFAULTS = {
311
- :topic => nil,
312
- :payload => ''
313
- }
314
-
315
- # Create a new Publish packet
316
- def initialize(args={})
317
- super(ATTR_DEFAULTS.merge(args))
318
- end
319
-
320
- def duplicate
321
- @flags[3]
322
- end
323
-
324
- # Set the DUP flag (true/false)
325
- def duplicate=(arg)
326
- if arg.kind_of?(Integer)
327
- @flags[3] = (arg == 0x1)
328
- else
329
- @flags[3] = arg
330
- end
331
- end
332
-
333
- def retain
334
- @flags[0]
335
- end
336
-
337
- # Set the retain flag (true/false)
338
- def retain=(arg)
339
- if arg.kind_of?(Integer)
340
- @flags[0] = (arg == 0x1)
341
- else
342
- @flags[0] = arg
343
- end
344
- end
345
-
346
- def qos
347
- (@flags[1] ? 0x01 : 0x00) | (@flags[2] ? 0x02 : 0x00)
348
- end
349
-
350
- # Set the Quality of Service level (0/1/2)
351
- def qos=(arg)
352
- @qos = arg.to_i
353
- if @qos < 0 or @qos > 2
354
- raise "Invalid QoS value: #{@qos}"
355
- else
356
- @flags[1] = (arg & 0x01 == 0x01)
357
- @flags[2] = (arg & 0x02 == 0x02)
358
- end
359
- end
360
-
361
- # Get serialisation of packet's body
362
- def encode_body
363
- body = ''
364
- if @topic.nil? or @topic.to_s.empty?
365
- raise "Invalid topic name when serialising packet"
366
- end
367
- body += encode_string(@topic)
368
- body += encode_short(@id) unless qos == 0
369
- body += payload.to_s.dup.force_encoding('ASCII-8BIT')
370
- return body
371
- end
372
-
373
- # Parse the body (variable header and payload) of a Publish packet
374
- def parse_body(buffer)
375
- super(buffer)
376
- @topic = shift_string(buffer)
377
- @id = shift_short(buffer) unless qos == 0
378
- @payload = buffer
379
- end
380
-
381
- # Check that fixed header flags are valid for this packet type
382
- # @private
383
- def validate_flags
384
- if qos == 3
385
- raise "Invalid packet: QoS value of 3 is not allowed"
386
- end
387
- if qos == 0 and duplicate
388
- raise "Invalid packet: DUP cannot be set for QoS 0"
389
- end
390
- end
391
-
392
- # Returns a human readable string, summarising the properties of the packet
393
- def inspect
394
- "\#<#{self.class}: " +
395
- "d#{duplicate ? '1' : '0'}, " +
396
- "q#{qos}, " +
397
- "r#{retain ? '1' : '0'}, " +
398
- "m#{id}, " +
399
- "'#{topic}', " +
400
- "#{inspect_payload}>"
401
- end
402
-
403
- protected
404
-
405
- def inspect_payload
406
- str = payload.to_s
407
- if str.bytesize < 16 and str =~ /^[ -~]*$/
408
- "'#{str}'"
409
- else
410
- "... (#{str.bytesize} bytes)"
411
- end
412
- end
413
- end
414
-
415
- # Class representing an MQTT Connect Packet
416
- class Connect < PahoMqtt::Packet
417
- # The name of the protocol
418
- attr_accessor :protocol_name
419
-
420
- # The version number of the protocol
421
- attr_accessor :protocol_level
422
-
423
- # The client identifier string
424
- attr_accessor :client_id
425
-
426
- # Set to false to keep a persistent session with the server
427
- attr_accessor :clean_session
428
-
429
- # Period the server should keep connection open for between pings
430
- attr_accessor :keep_alive
431
-
432
- # The topic name to send the Will message to
433
- attr_accessor :will_topic
434
-
435
- # The QoS level to send the Will message as
436
- attr_accessor :will_qos
437
-
438
- # Set to true to make the Will message retained
439
- attr_accessor :will_retain
440
-
441
- # The payload of the Will message
442
- attr_accessor :will_payload
443
-
444
- # The username for authenticating with the server
445
- attr_accessor :username
446
-
447
- # The password for authenticating with the server
448
- attr_accessor :password
449
-
450
- # Default attribute values
451
- ATTR_DEFAULTS = {
452
- :client_id => nil,
453
- :clean_session => true,
454
- :keep_alive => 15,
455
- :will_topic => nil,
456
- :will_qos => 0,
457
- :will_retain => false,
458
- :will_payload => '',
459
- :username => nil,
460
- :password => nil,
461
- }
462
-
463
- # Create a new Client Connect packet
464
- def initialize(args={})
465
- super(ATTR_DEFAULTS.merge(args))
466
-
467
- if version == '3.1.0' or version == '3.1'
468
- self.protocol_name ||= 'MQIsdp'
469
- self.protocol_level ||= 0x03
470
- elsif version == '3.1.1'
471
- self.protocol_name ||= 'MQTT'
472
- self.protocol_level ||= 0x04
473
- else
474
- raise ArgumentError.new("Unsupported protocol version: #{version}")
475
- end
476
- end
477
-
478
- # Get serialisation of packet's body
479
- def encode_body
480
- body = ''
481
- if @version == '3.1.0'
482
- if @client_id.nil? or @client_id.bytesize < 1
483
- raise "Client identifier too short while serialising packet"
484
- elsif @client_id.bytesize > 23
485
- raise "Client identifier too long when serialising packet"
486
- end
487
- end
488
- body += encode_string(@protocol_name)
489
- body += encode_bytes(@protocol_level.to_i)
490
- if @keep_alive < 0
491
- raise "Invalid keep-alive value: cannot be less than 0"
492
- end
493
-
494
- # Set the Connect flags
495
- @connect_flags = 0
496
- @connect_flags |= 0x02 if @clean_session
497
- @connect_flags |= 0x04 unless @will_topic.nil?
498
- @connect_flags |= ((@will_qos & 0x03) << 3)
499
- @connect_flags |= 0x20 if @will_retain
500
- @connect_flags |= 0x40 unless @password.nil?
501
- @connect_flags |= 0x80 unless @username.nil?
502
- body += encode_bytes(@connect_flags)
503
- body += encode_short(@keep_alive)
504
- body += encode_string(@client_id)
505
- unless will_topic.nil?
506
- body += encode_string(@will_topic)
507
- # The MQTT v3.1 specification says that the payload is a UTF-8 string
508
- body += encode_string(@will_payload)
509
- end
510
- body += encode_string(@username) unless @username.nil?
511
- body += encode_string(@password) unless @password.nil?
512
- return body
513
- end
514
-
515
- # Parse the body (variable header and payload) of a Connect packet
516
- def parse_body(buffer)
517
- super(buffer)
518
- @protocol_name = shift_string(buffer)
519
- @protocol_level = shift_byte(buffer).to_i
520
- if @protocol_name == 'MQIsdp' and @protocol_level == 3
521
- @version = '3.1.0'
522
- elsif @protocol_name == 'MQTT' and @protocol_level == 4
523
- @version = '3.1.1'
524
- else
525
- raise "Unsupported protocol: #{@protocol_name}/#{@protocol_level}"
526
- end
527
-
528
- @connect_flags = shift_byte(buffer)
529
- @clean_session = ((@connect_flags & 0x02) >> 1) == 0x01
530
- @keep_alive = shift_short(buffer)
531
- @client_id = shift_string(buffer)
532
- if ((@connect_flags & 0x04) >> 2) == 0x01
533
- # Last Will and Testament
534
- @will_qos = ((@connect_flags & 0x18) >> 3)
535
- @will_retain = ((@connect_flags & 0x20) >> 5) == 0x01
536
- @will_topic = shift_string(buffer)
537
- # The MQTT v3.1 specification says that the payload is a UTF-8 string
538
- @will_payload = shift_string(buffer)
539
- end
540
- if ((@connect_flags & 0x80) >> 7) == 0x01 and buffer.bytesize > 0
541
- @username = shift_string(buffer)
542
- end
543
- if ((@connect_flags & 0x40) >> 6) == 0x01 and buffer.bytesize > 0
544
- @password = shift_string(buffer)
545
- end
546
- end
547
-
548
- # Returns a human readable string, summarising the properties of the packet
549
- def inspect
550
- str = "\#<#{self.class}: "
551
- str += "keep_alive=#{keep_alive}"
552
- str += ", clean" if clean_session
553
- str += ", client_id='#{client_id}'"
554
- str += ", username='#{username}'" unless username.nil?
555
- str += ", password=..." unless password.nil?
556
- str += ">"
557
- end
558
- end
559
-
560
- # Class representing an MQTT Connect Acknowledgment Packet
561
- class Connack < PahoMqtt::Packet
562
- # Session Present flag
563
- attr_accessor :session_present
564
-
565
- # The return code (defaults to 0 for connection accepted)
566
- attr_accessor :return_code
567
-
568
- # Default attribute values
569
- ATTR_DEFAULTS = {:return_code => 0x00}
570
-
571
- # Create a new Client Connect packet
572
- def initialize(args={})
573
- # We must set flags before other attributes
574
- @connack_flags = [false, false, false, false, false, false, false, false]
575
- super(ATTR_DEFAULTS.merge(args))
576
- end
577
-
578
- # Get the Session Present flag
579
- def session_present
580
- @connack_flags[0]
581
- end
582
-
583
- # Set the Session Present flag
584
- def session_present=(arg)
585
- if arg.kind_of?(Integer)
586
- @connack_flags[0] = (arg == 0x1)
587
- else
588
- @connack_flags[0] = arg
589
- end
590
- end
591
-
592
- # Get a string message corresponding to a return code
593
- def return_msg
594
- case return_code
595
- when 0x00
596
- "Connection Accepted"
597
- when 0x01
598
- "Connection refused: unacceptable protocol version"
599
- when 0x02
600
- "Connection refused: client identifier rejected"
601
- when 0x03
602
- "Connection refused: server unavailable"
603
- when 0x04
604
- "Connection refused: bad user name or password"
605
- when 0x05
606
- "Connection refused: not authorised"
607
- else
608
- "Connection refused: error code #{return_code}"
609
- end
610
- end
611
-
612
- # Get serialisation of packet's body
613
- def encode_body
614
- body = ''
615
- body += encode_bits(@connack_flags)
616
- body += encode_bytes(@return_code.to_i)
617
- return body
618
- end
619
-
620
- # Parse the body (variable header and payload) of a Connect Acknowledgment packet
621
- def parse_body(buffer)
622
- super(buffer)
623
- @connack_flags = shift_bits(buffer)
624
- unless @connack_flags[1,7] == [false, false, false, false, false, false, false]
625
- raise "Invalid flags in Connack variable header"
626
- end
627
- @return_code = shift_byte(buffer)
628
- unless buffer.empty?
629
- raise "Extra bytes at end of Connect Acknowledgment packet"
630
- end
631
- end
632
-
633
- # Returns a human readable string, summarising the properties of the packet
634
- def inspect
635
- "\#<#{self.class}: 0x%2.2X>" % return_code
636
- end
637
- end
638
-
639
- # Class representing an MQTT Publish Acknowledgment packet
640
- class Puback < PahoMqtt::Packet
641
- # Get serialisation of packet's body
642
- def encode_body
643
- encode_short(@id)
644
- end
645
-
646
- # Parse the body (variable header and payload) of a packet
647
- def parse_body(buffer)
648
- super(buffer)
649
- @id = shift_short(buffer)
650
- unless buffer.empty?
651
- raise "Extra bytes at end of Publish Acknowledgment packet"
652
- end
653
- end
654
-
655
- # Returns a human readable string, summarising the properties of the packet
656
- def inspect
657
- "\#<#{self.class}: 0x%2.2X>" % id
658
- end
659
- end
660
-
661
- # Class representing an MQTT Publish Received packet
662
- class Pubrec < PahoMqtt::Packet
663
- # Get serialisation of packet's body
664
- def encode_body
665
- encode_short(@id)
666
- end
667
-
668
- # Parse the body (variable header and payload) of a packet
669
- def parse_body(buffer)
670
- super(buffer)
671
- @id = shift_short(buffer)
672
- unless buffer.empty?
673
- raise "Extra bytes at end of Publish Received packet"
674
- end
675
- end
676
-
677
- # Returns a human readable string, summarising the properties of the packet
678
- def inspect
679
- "\#<#{self.class}: 0x%2.2X>" % id
680
- end
681
- end
682
-
683
- # Class representing an MQTT Publish Release packet
684
- class Pubrel < PahoMqtt::Packet
685
-
686
- # Default attribute values
687
- ATTR_DEFAULTS = {
688
- :flags => [false, true, false, false],
689
- }
690
-
691
- # Create a new Pubrel packet
692
- def initialize(args={})
693
- super(ATTR_DEFAULTS.merge(args))
694
- end
695
-
696
- # Get serialisation of packet's body
697
- def encode_body
698
- encode_short(@id)
699
- end
700
-
701
- # Parse the body (variable header and payload) of a packet
702
- def parse_body(buffer)
703
- super(buffer)
704
- @id = shift_short(buffer)
705
- unless buffer.empty?
706
- raise "Extra bytes at end of Publish Release packet"
707
- end
708
- end
709
-
710
- # Check that fixed header flags are valid for this packet type
711
- # @private
712
- def validate_flags
713
- if @flags != [false, true, false, false]
714
- raise "Invalid flags in PUBREL packet header"
715
- end
716
- end
717
-
718
- # Returns a human readable string, summarising the properties of the packet
719
- def inspect
720
- "\#<#{self.class}: 0x%2.2X>" % id
721
- end
722
- end
723
-
724
- # Class representing an MQTT Publish Complete packet
725
- class Pubcomp < PahoMqtt::Packet
726
- # Get serialisation of packet's body
727
- def encode_body
728
- encode_short(@id)
729
- end
730
-
731
- # Parse the body (variable header and payload) of a packet
732
- def parse_body(buffer)
733
- super(buffer)
734
- @id = shift_short(buffer)
735
- unless buffer.empty?
736
- raise "Extra bytes at end of Publish Complete packet"
737
- end
738
- end
739
-
740
- # Returns a human readable string, summarising the properties of the packet
741
- def inspect
742
- "\#<#{self.class}: 0x%2.2X>" % id
743
- end
744
- end
745
-
746
- # Class representing an MQTT Client Subscribe packet
747
- class Subscribe < PahoMqtt::Packet
748
- # One or more topic filters to subscribe to
749
- attr_accessor :topics
750
-
751
- # Default attribute values
752
- ATTR_DEFAULTS = {
753
- :topics => [],
754
- :flags => [false, true, false, false],
755
- }
756
-
757
- # Create a new Subscribe packet
758
- def initialize(args={})
759
- super(ATTR_DEFAULTS.merge(args))
760
- end
761
-
762
- # Set one or more topic filters for the Subscribe packet
763
- # The topics parameter should be one of the following:
764
- # * String: subscribe to one topic with QoS 0
765
- # * Array: subscribe to multiple topics with QoS 0
766
- # * Hash: subscribe to multiple topics where the key is the topic and the value is the QoS level
767
- #
768
- # For example:
769
- # packet.topics = 'a/b'
770
- # packet.topics = ['a/b', 'c/d']
771
- # packet.topics = [['a/b',0], ['c/d',1]]
772
- # packet.topics = {'a/b' => 0, 'c/d' => 1}
773
- #
774
- def topics=(value)
775
- # Get input into a consistent state
776
- if value.is_a?(Array)
777
- input = value.flatten
778
- else
779
- input = [value]
780
- end
781
-
782
- @topics = []
783
- while(input.length>0)
784
- item = input.shift
785
- if item.is_a?(Hash)
786
- # Convert hash into an ordered array of arrays
787
- @topics += item.sort
788
- elsif item.is_a?(String)
789
- # Peek at the next item in the array, and remove it if it is an integer
790
- if input.first.is_a?(Integer)
791
- qos = input.shift
792
- @topics << [item,qos]
793
- else
794
- @topics << [item,0]
795
- end
796
- else
797
- # Meh?
798
- raise "Invalid topics input: #{value.inspect}"
799
- end
800
- end
801
- @topics
802
- end
803
-
804
- # Get serialisation of packet's body
805
- def encode_body
806
- if @topics.empty?
807
- raise "no topics given when serialising packet"
808
- end
809
- body = encode_short(@id)
810
- topics.each do |item|
811
- body += encode_string(item[0])
812
- body += encode_bytes(item[1])
813
- end
814
- return body
815
- end
816
-
817
- # Parse the body (variable header and payload) of a packet
818
- def parse_body(buffer)
819
- super(buffer)
820
- @id = shift_short(buffer)
821
- @topics = []
822
- while(buffer.bytesize>0)
823
- topic_name = shift_string(buffer)
824
- topic_qos = shift_byte(buffer)
825
- @topics << [topic_name,topic_qos]
826
- end
827
- end
828
-
829
- # Check that fixed header flags are valid for this packet type
830
- # @private
831
- def validate_flags
832
- if @flags != [false, true, false, false]
833
- raise "Invalid flags in SUBSCRIBE packet header"
834
- end
835
- end
836
-
837
- # Returns a human readable string, summarising the properties of the packet
838
- def inspect
839
- _str = "\#<#{self.class}: 0x%2.2X, %s>" % [
840
- id,
841
- topics.map {|t| "'#{t[0]}':#{t[1]}"}.join(', ')
842
- ]
843
- end
844
- end
845
-
846
- # Class representing an MQTT Subscribe Acknowledgment packet
847
- class Suback < PahoMqtt::Packet
848
- # An array of return codes, ordered by the topics that were subscribed to
849
- attr_accessor :return_codes
850
-
851
- # Default attribute values
852
- ATTR_DEFAULTS = {
853
- :return_codes => [],
854
- }
855
-
856
- # Create a new Subscribe Acknowledgment packet
857
- def initialize(args={})
858
- super(ATTR_DEFAULTS.merge(args))
859
- end
860
-
861
- # Set the granted QoS value for each of the topics that were subscribed to
862
- # Can either be an integer or an array or integers.
863
- def return_codes=(value)
864
- if value.is_a?(Array)
865
- @return_codes = value
866
- elsif value.is_a?(Integer)
867
- @return_codes = [value]
868
- else
869
- raise "return_codes should be an integer or an array of return codes"
870
- end
871
- end
872
-
873
- # Get serialisation of packet's body
874
- def encode_body
875
- if @return_codes.empty?
876
- raise "no granted QoS given when serialising packet"
877
- end
878
- body = encode_short(@id)
879
- return_codes.each { |qos| body += encode_bytes(qos) }
880
- return body
881
- end
882
-
883
- # Parse the body (variable header and payload) of a packet
884
- def parse_body(buffer)
885
- super(buffer)
886
- @id = shift_short(buffer)
887
- while(buffer.bytesize>0)
888
- @return_codes << shift_byte(buffer)
889
- end
890
- end
891
-
892
- # Returns a human readable string, summarising the properties of the packet
893
- def inspect
894
- "\#<#{self.class}: 0x%2.2X, rc=%s>" % [id, return_codes.map{|rc| "0x%2.2X" % rc}.join(',')]
895
- end
896
- end
897
-
898
- # Class representing an MQTT Client Unsubscribe packet
899
- class Unsubscribe < PahoMqtt::Packet
900
- # One or more topic paths to unsubscribe from
901
- attr_accessor :topics
902
-
903
- # Default attribute values
904
- ATTR_DEFAULTS = {
905
- :topics => [],
906
- :flags => [false, true, false, false],
907
- }
908
-
909
- # Create a new Unsubscribe packet
910
- def initialize(args={})
911
- super(ATTR_DEFAULTS.merge(args))
912
- end
913
-
914
- # Set one or more topic paths to unsubscribe from
915
- def topics=(value)
916
- if value.is_a?(Array)
917
- @topics = value
918
- else
919
- @topics = [value]
920
- end
921
- end
922
-
923
- # Get serialisation of packet's body
924
- def encode_body
925
- if @topics.empty?
926
- raise "no topics given when serialising packet"
927
- end
928
- body = encode_short(@id)
929
- topics.each { |topic| body += encode_string(topic) }
930
- return body
931
- end
932
-
933
- # Parse the body (variable header and payload) of a packet
934
- def parse_body(buffer)
935
- super(buffer)
936
- @id = shift_short(buffer)
937
- while(buffer.bytesize>0)
938
- @topics << shift_string(buffer)
939
- end
940
- end
941
-
942
- # Check that fixed header flags are valid for this packet type
943
- # @private
944
- def validate_flags
945
- if @flags != [false, true, false, false]
946
- raise "Invalid flags in UNSUBSCRIBE packet header"
947
- end
948
- end
949
-
950
- # Returns a human readable string, summarising the properties of the packet
951
- def inspect
952
- "\#<#{self.class}: 0x%2.2X, %s>" % [
953
- id,
954
- topics.map {|t| "'#{t}'"}.join(', ')
955
- ]
956
- end
957
- end
958
-
959
- # Class representing an MQTT Unsubscribe Acknowledgment packet
960
- class Unsuback < PahoMqtt::Packet
961
- # Create a new Unsubscribe Acknowledgment packet
962
- def initialize(args={})
963
- super(args)
964
- end
965
-
966
- # Get serialisation of packet's body
967
- def encode_body
968
- encode_short(@id)
969
- end
970
-
971
- # Parse the body (variable header and payload) of a packet
972
- def parse_body(buffer)
973
- super(buffer)
974
- @id = shift_short(buffer)
975
- unless buffer.empty?
976
- raise "Extra bytes at end of Unsubscribe Acknowledgment packet"
977
- end
978
- end
979
-
980
- # Returns a human readable string, summarising the properties of the packet
981
- def inspect
982
- "\#<#{self.class}: 0x%2.2X>" % id
983
- end
984
- end
985
-
986
- # Class representing an MQTT Ping Request packet
987
- class Pingreq < PahoMqtt::Packet
988
- # Create a new Ping Request packet
989
- def initialize(args={})
990
- super(args)
991
- end
992
-
993
- # Check the body
994
- def parse_body(buffer)
995
- super(buffer)
996
- unless buffer.empty?
997
- raise "Extra bytes at end of Ping Request packet"
998
- end
999
- end
1000
- end
1001
-
1002
- # Class representing an MQTT Ping Response packet
1003
- class Pingresp < PahoMqtt::Packet
1004
- # Create a new Ping Response packet
1005
- def initialize(args={})
1006
- super(args)
1007
- end
1008
-
1009
- # Check the body
1010
- def parse_body(buffer)
1011
- super(buffer)
1012
- unless buffer.empty?
1013
- raise "Extra bytes at end of Ping Response packet"
1014
- end
1015
- end
1016
- end
1017
-
1018
- # Class representing an MQTT Client Disconnect packet
1019
- class Disconnect < PahoMqtt::Packet
1020
- # Create a new Client Disconnect packet
1021
- def initialize(args={})
1022
- super(args)
1023
- end
1024
-
1025
- # Check the body
1026
- def parse_body(buffer)
1027
- super(buffer)
1028
- unless buffer.empty?
1029
- raise "Extra bytes at end of Disconnect packet"
1030
- end
1031
- end
1032
- end
1033
- end
1034
-
1035
-
1036
- # An enumeration of the MQTT packet types
1037
- PACKET_TYPES = [
1038
- nil,
1039
- PahoMqtt::Packet::Connect,
1040
- PahoMqtt::Packet::Connack,
1041
- PahoMqtt::Packet::Publish,
1042
- PahoMqtt::Packet::Puback,
1043
- PahoMqtt::Packet::Pubrec,
1044
- PahoMqtt::Packet::Pubrel,
1045
- PahoMqtt::Packet::Pubcomp,
1046
- PahoMqtt::Packet::Subscribe,
1047
- PahoMqtt::Packet::Suback,
1048
- PahoMqtt::Packet::Unsubscribe,
1049
- PahoMqtt::Packet::Unsuback,
1050
- PahoMqtt::Packet::Pingreq,
1051
- PahoMqtt::Packet::Pingresp,
1052
- PahoMqtt::Packet::Disconnect,
1053
- nil
1054
- ]
1055
-
1056
- end