apiotics-paho-mqtt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/CODE_OF_CONDUCT.md +49 -0
  6. data/Gemfile +4 -0
  7. data/README.md +322 -0
  8. data/Rakefile +6 -0
  9. data/apiotics-paho-mqtt.gemspec +33 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/lib/paho-mqtt.rb +165 -0
  13. data/lib/paho_mqtt/client.rb +417 -0
  14. data/lib/paho_mqtt/connection_helper.rb +169 -0
  15. data/lib/paho_mqtt/exception.rb +43 -0
  16. data/lib/paho_mqtt/handler.rb +273 -0
  17. data/lib/paho_mqtt/packet/base.rb +315 -0
  18. data/lib/paho_mqtt/packet/connack.rb +102 -0
  19. data/lib/paho_mqtt/packet/connect.rb +183 -0
  20. data/lib/paho_mqtt/packet/disconnect.rb +38 -0
  21. data/lib/paho_mqtt/packet/pingreq.rb +29 -0
  22. data/lib/paho_mqtt/packet/pingresp.rb +38 -0
  23. data/lib/paho_mqtt/packet/puback.rb +44 -0
  24. data/lib/paho_mqtt/packet/pubcomp.rb +44 -0
  25. data/lib/paho_mqtt/packet/publish.rb +148 -0
  26. data/lib/paho_mqtt/packet/pubrec.rb +44 -0
  27. data/lib/paho_mqtt/packet/pubrel.rb +62 -0
  28. data/lib/paho_mqtt/packet/suback.rb +75 -0
  29. data/lib/paho_mqtt/packet/subscribe.rb +124 -0
  30. data/lib/paho_mqtt/packet/unsuback.rb +49 -0
  31. data/lib/paho_mqtt/packet/unsubscribe.rb +84 -0
  32. data/lib/paho_mqtt/packet.rb +33 -0
  33. data/lib/paho_mqtt/publisher.rb +191 -0
  34. data/lib/paho_mqtt/sender.rb +86 -0
  35. data/lib/paho_mqtt/ssl_helper.rb +42 -0
  36. data/lib/paho_mqtt/subscriber.rb +163 -0
  37. data/lib/paho_mqtt/version.rb +3 -0
  38. data/samples/client_blocking(reading).rb +30 -0
  39. data/samples/client_blocking(writing).rb +18 -0
  40. data/samples/getting_started.rb +49 -0
  41. data/samples/test_client.rb +70 -0
  42. metadata +127 -0
@@ -0,0 +1,273 @@
1
+ # Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
2
+ #
3
+ # All rights reserved. This program and the accompanying materials
4
+ # are made available under the terms of the Eclipse Public License v1.0
5
+ # and Eclipse Distribution License v1.0 which accompany this distribution.
6
+ #
7
+ # The Eclipse Public License is available at
8
+ # https://eclipse.org/org/documents/epl-v10.php.
9
+ # and the Eclipse Distribution License is available at
10
+ # https://eclipse.org/org/documents/edl-v10.php.
11
+ #
12
+ # Contributors:
13
+ # Pierre Goudet - initial committer
14
+
15
+ module PahoMqtt
16
+ class Handler
17
+
18
+ attr_reader :registered_callback
19
+ attr_accessor :last_ping_resp
20
+ attr_accessor :clean_session
21
+
22
+ def initialize
23
+ @registered_callback = []
24
+ @last_ping_resp = -1
25
+ @publisher = nil
26
+ @subscriber = nil
27
+ end
28
+
29
+ def config_pubsub(publisher, subscriber)
30
+ @publisher = publisher
31
+ @subscriber = subscriber
32
+ end
33
+
34
+ def socket=(socket)
35
+ @socket = socket
36
+ end
37
+
38
+ def receive_packet
39
+ result = IO.select([@socket], [], [], SELECT_TIMEOUT) unless @socket.nil? || @socket.closed?
40
+ unless result.nil?
41
+ packet = PahoMqtt::Packet::Base.read(@socket)
42
+ unless packet.nil?
43
+ if packet.is_a?(PahoMqtt::Packet::Connack)
44
+ @last_ping_resp = Time.now
45
+ handle_connack(packet)
46
+ else
47
+ handle_packet(packet)
48
+ @last_ping_resp = Time.now
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def handle_packet(packet)
55
+ PahoMqtt.logger.info("New packet #{packet.class} received.") if PahoMqtt.logger?
56
+ type = packet_type(packet)
57
+ self.send("handle_#{type}", packet)
58
+ end
59
+
60
+ def register_topic_callback(topic, callback, &block)
61
+ if topic.nil?
62
+ PahoMqtt.logger.error("The topics where the callback is trying to be registered have been found nil.") if PahoMqtt.logger?
63
+ raise ArgumentError
64
+ end
65
+ clear_topic_callback(topic)
66
+ if block_given?
67
+ @registered_callback.push([topic, block])
68
+ elsif !(callback.nil?) && callback.is_a?(Proc)
69
+ @registered_callback.push([topic, callback])
70
+ end
71
+ MQTT_ERR_SUCCESS
72
+ end
73
+
74
+ def clear_topic_callback(topic)
75
+ if topic.nil?
76
+ PahoMqtt.logger.error("The topics where the callback is trying to be unregistered have been found nil.") if PahoMqtt.logger?
77
+ raise ArgumentError
78
+ end
79
+ @registered_callback.delete_if { |pair| pair.first == topic }
80
+ MQTT_ERR_SUCCESS
81
+ end
82
+
83
+ def handle_connack(packet)
84
+ if packet.return_code == 0x00
85
+ PahoMqtt.logger.debug(packet.return_msg) if PahoMqtt.logger?
86
+ handle_connack_accepted(packet.session_present)
87
+ else
88
+ PahoMqtt.logger.warm(packet.return_msg) if PahoMqtt.logger?
89
+ MQTT_CS_DISCONNECTED
90
+ end
91
+ @on_connack.call(packet) unless @on_connack.nil?
92
+ MQTT_CS_CONNECTED
93
+ end
94
+
95
+ def handle_connack_accepted(session_flag)
96
+ clean_session?(session_flag)
97
+ new_session?(session_flag)
98
+ old_session?(session_flag)
99
+ end
100
+
101
+ def new_session?(session_flag)
102
+ if !@clean_session && !session_flag
103
+ PahoMqtt.logger.debug("New session created for the client.") if PahoMqtt.logger?
104
+ end
105
+ end
106
+
107
+ def clean_session?(session_flag)
108
+ if @clean_session && !session_flag
109
+ PahoMqtt.logger.debug("No previous session found by server, starting a new one.") if PahoMqtt.logger?
110
+ end
111
+ end
112
+
113
+ def old_session?(session_flag)
114
+ if !@clean_session && session_flag
115
+ PahoMqtt.logger.debug("Previous session restored by the server.") if PahoMqtt.logger?
116
+ end
117
+ end
118
+
119
+ def handle_pingresp(_packet)
120
+ @last_ping_resp = Time.now
121
+ end
122
+
123
+ def handle_suback(packet)
124
+ max_qos = packet.return_codes
125
+ id = packet.id
126
+ topics = []
127
+ topics = @subscriber.add_subscription(max_qos, id, topics)
128
+ unless topics.empty?
129
+ @on_suback.call(topics) unless @on_suback.nil?
130
+ end
131
+ end
132
+
133
+ def handle_unsuback(packet)
134
+ id = packet.id
135
+ topics = []
136
+ topics = @subscriber.remove_subscription(id, topics)
137
+ unless topics.empty?
138
+ @on_unsuback.call(topics) unless @on_unsuback.nil?
139
+ end
140
+ end
141
+
142
+ def handle_publish(packet)
143
+ id = packet.id
144
+ qos = packet.qos
145
+ if @publisher.do_publish(qos, id) == MQTT_ERR_SUCCESS
146
+ @on_message.call(packet) unless @on_message.nil?
147
+ check_callback(packet)
148
+ end
149
+ end
150
+
151
+ def handle_puback(packet)
152
+ id = packet.id
153
+ if @publisher.do_puback(id) == MQTT_ERR_SUCCESS
154
+ @on_puback.call(packet) unless @on_puback.nil?
155
+ end
156
+ end
157
+
158
+ def handle_pubrec(packet)
159
+ id = packet.id
160
+ if @publisher.do_pubrec(id) == MQTT_ERR_SUCCESS
161
+ @on_pubrec.call(packet) unless @on_pubrec.nil?
162
+ end
163
+ end
164
+
165
+ def handle_pubrel(packet)
166
+ id = packet.id
167
+ if @publisher.do_pubrel(id) == MQTT_ERR_SUCCESS
168
+ @on_pubrel.call(packet) unless @on_pubrel.nil?
169
+ end
170
+ end
171
+
172
+ def handle_pubcomp(packet)
173
+ id = packet.id
174
+ if @publisher.do_pubcomp(id) == MQTT_ERR_SUCCESS
175
+ @on_pubcomp.call(packet) unless @on_pubcomp.nil?
176
+ end
177
+ end
178
+
179
+ def on_connack(&block)
180
+ @on_connack = block if block_given?
181
+ @on_connack
182
+ end
183
+
184
+ def on_suback(&block)
185
+ @on_suback = block if block_given?
186
+ @on_suback
187
+ end
188
+
189
+ def on_unsuback(&block)
190
+ @on_unsuback = block if block_given?
191
+ @on_unsuback
192
+ end
193
+
194
+ def on_puback(&block)
195
+ @on_puback = block if block_given?
196
+ @on_puback
197
+ end
198
+
199
+ def on_pubrec(&block)
200
+ @on_pubrec = block if block_given?
201
+ @on_pubrec
202
+ end
203
+
204
+ def on_pubrel(&block)
205
+ @on_pubrel = block if block_given?
206
+ @on_pubrel
207
+ end
208
+
209
+ def on_pubcomp(&block)
210
+ @on_pubcomp = block if block_given?
211
+ @on_pubcomp
212
+ end
213
+
214
+ def on_message(&block)
215
+ @on_message = block if block_given?
216
+ @on_message
217
+ end
218
+
219
+ def on_connack=(callback)
220
+ @on_connack = callback if callback.is_a?(Proc)
221
+ end
222
+
223
+ def on_suback=(callback)
224
+ @on_suback = callback if callback.is_a?(Proc)
225
+ end
226
+
227
+ def on_unsuback=(callback)
228
+ @on_unsuback = callback if callback.is_a?(Proc)
229
+ end
230
+
231
+ def on_puback=(callback)
232
+ @on_puback = callback if callback.is_a?(Proc)
233
+ end
234
+
235
+ def on_pubrec=(callback)
236
+ @on_pubrec = callback if callback.is_a?(Proc)
237
+ end
238
+
239
+ def on_pubrel=(callback)
240
+ @on_pubrel = callback if callback.is_a?(Proc)
241
+ end
242
+
243
+ def on_pubcomp=(callback)
244
+ @on_pubcomp = callback if callback.is_a?(Proc)
245
+ end
246
+
247
+ def on_message=(callback)
248
+ @on_message = callback if callback.is_a?(Proc)
249
+ end
250
+
251
+ def packet_type(packet)
252
+ type = packet.class
253
+ if PahoMqtt::PACKET_TYPES[3..13].include?(type)
254
+ type.to_s.split('::').last.downcase
255
+ else
256
+ PahoMqtt.logger.error("Received an unexpeceted packet: #{packet}.") if PahoMqtt.logger?
257
+ raise PacketException.new('Invalid packet type id')
258
+ end
259
+ end
260
+
261
+ def check_callback(packet)
262
+ callbacks = []
263
+ @registered_callback.each do |reccord|
264
+ callbacks.push(reccord.last) if PahoMqtt.match_filter(packet.topic, reccord.first)
265
+ end
266
+ unless callbacks.empty?
267
+ callbacks.each do |callback|
268
+ callback.call(packet)
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,315 @@
1
+ # encoding: BINARY
2
+ ### original file from the ruby-mqtt gem
3
+ ### located at https://github.com/njh/ruby-mqtt/blob/master/lib/mqtt/packet.rb
4
+ ### Copyright (c) 2009-2013 Nicholas J Humfrey
5
+
6
+ # Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
7
+ #
8
+ # All rights reserved. This program and the accompanying materials
9
+ # are made available under the terms of the Eclipse Public License v1.0
10
+ # and Eclipse Distribution License v1.0 which accompany this distribution.
11
+ #
12
+ # The Eclipse Public License is available at
13
+ # https://eclipse.org/org/documents/epl-v10.php.
14
+ # and the Eclipse Distribution License is available at
15
+ # https://eclipse.org/org/documents/edl-v10.php.
16
+ #
17
+ # Contributors:
18
+ # Pierre Goudet - initial committer
19
+
20
+ module PahoMqtt
21
+ module Packet
22
+ # Class representing a MQTT Packet
23
+ # Performs binary encoding and decoding of headers
24
+ class Base
25
+ # The version number of the MQTT protocol to use (default 3.1.0)
26
+ attr_accessor :version
27
+
28
+ # Identifier to link related control packets together
29
+ attr_accessor :id
30
+
31
+ # Array of 4 bits in the fixed header
32
+ attr_accessor :flags
33
+
34
+ # The length of the parsed packet body
35
+ attr_reader :body_length
36
+
37
+ # Default attribute values
38
+ ATTR_DEFAULTS = {
39
+ :version => '3.1.0',
40
+ :id => 0,
41
+ :body_length => nil
42
+ }
43
+
44
+ # Read in a packet from a socket
45
+ def self.read(socket)
46
+ # Read in the packet header and create a new packet object
47
+ packet = create_from_header(
48
+ read_byte(socket)
49
+ )
50
+
51
+ unless packet.nil?
52
+ packet.validate_flags
53
+
54
+ # Read in the packet length
55
+ multiplier = 1
56
+ body_length = 0
57
+ pos = 1
58
+ begin
59
+ digit = read_byte(socket)
60
+ body_length += ((digit & 0x7F) * multiplier)
61
+ multiplier *= 0x80
62
+ pos += 1
63
+ end while ((digit & 0x80) != 0x00) && pos <= 4
64
+
65
+ # Store the expected body length in the packet
66
+ packet.instance_variable_set('@body_length', body_length)
67
+
68
+ # Read in the packet body
69
+ packet.parse_body(socket.read(body_length))
70
+ end
71
+ packet
72
+ end
73
+
74
+ # Parse buffer into new packet object
75
+ def self.parse(buffer)
76
+ packet = parse_header(buffer)
77
+ packet.parse_body(buffer)
78
+ packet
79
+ end
80
+
81
+ # Parse the header and create a new packet object of the correct type
82
+ # The header is removed from the buffer passed into this function
83
+ def self.parse_header(buffer)
84
+ # Check that the packet is a long as the minimum packet size
85
+ if buffer.bytesize < 2
86
+ raise PahoMqtt::PacketFormatException.new(
87
+ "Invalid packet: less than 2 bytes long")
88
+ end
89
+
90
+ # Create a new packet object
91
+ bytes = buffer.unpack("C5")
92
+ packet = create_from_header(bytes.first)
93
+ packet.validate_flags
94
+
95
+ # Parse the packet length
96
+ body_length = 0
97
+ multiplier = 1
98
+ pos = 1
99
+ begin
100
+ if buffer.bytesize <= pos
101
+ raise PahoMqtt::PacketFormatException.new(
102
+ "The packet length header is incomplete")
103
+ end
104
+ digit = bytes[pos]
105
+ body_length += ((digit & 0x7F) * multiplier)
106
+ multiplier *= 0x80
107
+ pos += 1
108
+ end while ((digit & 0x80) != 0x00) && pos <= 4
109
+
110
+ # Store the expected body length in the packet
111
+ packet.instance_variable_set('@body_length', body_length)
112
+
113
+ # Delete the fixed header from the raw packet passed in
114
+ buffer.slice!(0...pos)
115
+
116
+ packet
117
+ end
118
+
119
+ # Create a new packet object from the first byte of a MQTT packet
120
+ def self.create_from_header(byte)
121
+ unless byte.nil?
122
+ # Work out the class
123
+ type_id = ((byte & 0xF0) >> 4)
124
+ packet_class = PahoMqtt::PACKET_TYPES[type_id]
125
+ if packet_class.nil?
126
+ raise PahoMqtt::PacketFormatException.new(
127
+ "Invalid packet type identifier: #{type_id}")
128
+ end
129
+
130
+ # Convert the last 4 bits of byte into array of true/false
131
+ flags = (0..3).map { |i| byte & (2 ** i) != 0 }
132
+
133
+ # Create a new packet object
134
+ packet_class.new(:flags => flags)
135
+ end
136
+ end
137
+
138
+ # Create a new empty packet
139
+ def initialize(args={})
140
+ # We must set flags before the other values
141
+ @flags = [false, false, false, false]
142
+ update_attributes(ATTR_DEFAULTS.merge(args))
143
+ end
144
+
145
+ # Set packet attributes from a hash of attribute names and values
146
+ def update_attributes(attr={})
147
+ attr.each_pair do |k,v|
148
+ if v.is_a?(Array) || v.is_a?(Hash)
149
+ send("#{k}=", v.dup)
150
+ else
151
+ send("#{k}=", v)
152
+ end
153
+ end
154
+ end
155
+
156
+ # Get the identifer for this packet type
157
+ def type_id
158
+ index = PahoMqtt::PACKET_TYPES.index(self.class)
159
+ if index.nil?
160
+ raise PahoMqtt::PacketFormatException.new(
161
+ "Invalid packet type: #{self.class}")
162
+ end
163
+ index
164
+ end
165
+
166
+ # Get the name of the packet type as a string in capitals
167
+ # (like the MQTT specification uses)
168
+ #
169
+ # Example: CONNACK
170
+ def type_name
171
+ self.class.name.split('::').last.upcase
172
+ end
173
+
174
+ # Set the protocol version number
175
+ def version=(arg)
176
+ @version = arg.to_s
177
+ end
178
+
179
+ # Set the length of the packet body
180
+ def body_length=(arg)
181
+ @body_length = arg.to_i
182
+ end
183
+
184
+ # Parse the body (variable header and payload) of a packet
185
+ def parse_body(buffer)
186
+ if buffer.bytesize != body_length
187
+ raise PahoMqtt::PacketFormatException.new(
188
+ "Failed to parse packet - input buffer (#{buffer.bytesize}) is not the same as the body length header (#{body_length})")
189
+ end
190
+ end
191
+
192
+ # Get serialisation of packet's body (variable header and payload)
193
+ def encode_body
194
+ '' # No body by default
195
+ end
196
+
197
+ # Serialise the packet
198
+ def to_s
199
+ # Encode the fixed header
200
+ header = [
201
+ ((type_id.to_i & 0x0F) << 4) |
202
+ (flags[3] ? 0x8 : 0x0) |
203
+ (flags[2] ? 0x4 : 0x0) |
204
+ (flags[1] ? 0x2 : 0x0) |
205
+ (flags[0] ? 0x1 : 0x0)
206
+ ]
207
+
208
+ # Get the packet's variable header and payload
209
+ body = self.encode_body
210
+
211
+ # Check that that packet isn't too big
212
+ body_length = body.bytesize
213
+ if body_length > 268435455
214
+ raise PahoMqtt::PacketFormatException.new(
215
+ "Error serialising packet: body is more than 256MB")
216
+ end
217
+
218
+ # Build up the body length field bytes
219
+ begin
220
+ digit = (body_length % 128)
221
+ body_length = (body_length / 128)
222
+ # if there are more digits to encode, set the top bit of this digit
223
+ digit |= 0x80 if (body_length > 0)
224
+ header.push(digit)
225
+ end while (body_length > 0)
226
+
227
+ # Convert header to binary and add on body
228
+ header.pack('C*') + body
229
+ end
230
+
231
+ # Check that fixed header flags are valid for types that don't use the flags
232
+ # @private
233
+ def validate_flags
234
+ if flags != [false, false, false, false]
235
+ raise PahoMqtt::PacketFormatException.new(
236
+ "Invalid flags in #{type_name} packet header")
237
+ end
238
+ end
239
+
240
+ # Returns a human readable string
241
+ def inspect
242
+ "\#<#{self.class}>"
243
+ end
244
+
245
+ protected
246
+
247
+ # Encode an array of bytes and return them
248
+ def encode_bytes(*bytes)
249
+ bytes.pack('C*')
250
+ end
251
+
252
+ # Encode an array of bits and return them
253
+ def encode_bits(bits)
254
+ [bits.map { |b| b ? '1' : '0' }.join].pack('b*')
255
+ end
256
+
257
+ # Encode a 16-bit unsigned integer and return it
258
+ def encode_short(val)
259
+ [val.to_i].pack('n')
260
+ end
261
+
262
+ # Encode a UTF-8 string and return it
263
+ # (preceded by the length of the string)
264
+ def encode_string(str)
265
+ str = str.to_s.encode('UTF-8')
266
+
267
+ # Force to binary, when assembling the packet
268
+ str.force_encoding('ASCII-8BIT')
269
+ encode_short(str.bytesize) + str
270
+ end
271
+
272
+ # Remove a 16-bit unsigned integer from the front of buffer
273
+ def shift_short(buffer)
274
+ bytes = buffer.slice!(0..1)
275
+ bytes.unpack('n').first
276
+ end
277
+
278
+ # Remove one byte from the front of the string
279
+ def shift_byte(buffer)
280
+ buffer.slice!(0...1).unpack('C').first
281
+ end
282
+
283
+ # Remove 8 bits from the front of buffer
284
+ def shift_bits(buffer)
285
+ buffer.slice!(0...1).unpack('b8').first.split('').map { |b| b == '1' }
286
+ end
287
+
288
+ # Remove n bytes from the front of buffer
289
+ def shift_data(buffer,bytes)
290
+ buffer.slice!(0...bytes)
291
+ end
292
+
293
+ # Remove string from the front of buffer
294
+ def shift_string(buffer)
295
+ len = shift_short(buffer)
296
+ str = shift_data(buffer,len)
297
+ # Strings in MQTT v3.1 are all UTF-8
298
+ str.force_encoding('UTF-8')
299
+ end
300
+
301
+
302
+ private
303
+
304
+ # Read and unpack a single byte from a socket
305
+ def self.read_byte(socket)
306
+ byte = socket.read(1)
307
+ unless byte.nil?
308
+ byte.unpack('C').first
309
+ else
310
+ nil
311
+ end
312
+ end
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,102 @@
1
+ # encoding: BINARY
2
+ ### original file from the ruby-mqtt gem
3
+ ### located at https://github.com/njh/ruby-mqtt/blob/master/lib/mqtt/packet.rb
4
+ ### Copyright (c) 2009-2013 Nicholas J Humfrey
5
+
6
+ # Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
7
+ #
8
+ # All rights reserved. This program and the accompanying materials
9
+ # are made available under the terms of the Eclipse Public License v1.0
10
+ # and Eclipse Distribution License v1.0 which accompany this distribution.
11
+ #
12
+ # The Eclipse Public License is available at
13
+ # https://eclipse.org/org/documents/epl-v10.php.
14
+ # and the Eclipse Distribution License is available at
15
+ # https://eclipse.org/org/documents/edl-v10.php.
16
+ #
17
+ # Contributors:
18
+ # Pierre Goudet - initial committer
19
+
20
+ module PahoMqtt
21
+ module Packet
22
+ class Connack < PahoMqtt::Packet::Base
23
+ # Session Present flag
24
+ attr_accessor :session_present
25
+
26
+ # The return code (defaults to 0 for connection accepted)
27
+ attr_accessor :return_code
28
+
29
+ # Default attribute values
30
+ ATTR_DEFAULTS = { :return_code => 0x00 }
31
+
32
+ # Create a new Client Connect packet
33
+ def initialize(args={})
34
+ # We must set flags before other attributes
35
+ @connack_flags = [false, false, false, false, false, false, false, false]
36
+ super(ATTR_DEFAULTS.merge(args))
37
+ end
38
+
39
+ # Get the Session Present flag
40
+ def session_present
41
+ @connack_flags[0]
42
+ end
43
+
44
+ # Set the Session Present flag
45
+ def session_present=(arg)
46
+ if arg.kind_of?(Integer)
47
+ @connack_flags[0] = (arg == 0x1)
48
+ else
49
+ @connack_flags[0] = arg
50
+ end
51
+ end
52
+
53
+ # Get a string message corresponding to a return code
54
+ def return_msg
55
+ case return_code
56
+ when 0x00
57
+ "Connection accepted"
58
+ when 0x01
59
+ raise LowVersionException
60
+ when 0x02
61
+ "Connection refused: client identifier rejected"
62
+ when 0x03
63
+ "Connection refused: server unavailable"
64
+ when 0x04
65
+ "Connection refused: bad user name or password"
66
+ when 0x05
67
+ "Connection refused: not authorised"
68
+ else
69
+ "Connection refused: error code #{return_code}"
70
+ end
71
+ end
72
+
73
+ # Get serialisation of packet's body
74
+ def encode_body
75
+ body = ''
76
+ body += encode_bits(@connack_flags)
77
+ body += encode_bytes(@return_code.to_i)
78
+ body
79
+ end
80
+
81
+ # Parse the body (variable header and payload) of a Connect Acknowledgment packet
82
+ def parse_body(buffer)
83
+ super(buffer)
84
+ @connack_flags = shift_bits(buffer)
85
+ unless @connack_flags[1, 7] == [false, false, false, false, false, false, false]
86
+ raise PacketFormatException.new(
87
+ "Invalid flags in Connack variable header")
88
+ end
89
+ @return_code = shift_byte(buffer)
90
+ unless buffer.empty?
91
+ raise PacketFormatException.new(
92
+ "Extra bytes at end of Connect Acknowledgment packet")
93
+ end
94
+ end
95
+
96
+ # Returns a human readable string, summarising the properties of the packet
97
+ def inspect
98
+ "\#<#{self.class}: 0x%2.2X>" % return_code
99
+ end
100
+ end
101
+ end
102
+ end