mqtt 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/GPL +340 -0
- data/NEWS +19 -11
- data/README +41 -32
- data/lib/mqtt.rb +14 -4
- data/lib/mqtt/client.rb +223 -222
- data/lib/mqtt/packet.rb +330 -150
- data/lib/mqtt/proxy.rb +96 -108
- data/lib/mqtt/version.rb +3 -0
- data/spec/mqtt_client_spec.rb +303 -0
- data/spec/mqtt_packet_spec.rb +1230 -0
- data/spec/mqtt_proxy_spec.rb +8 -0
- data/spec/mqtt_version_spec.rb +23 -0
- metadata +136 -25
- data/Rakefile +0 -112
- data/examples/livetext.rb +0 -14
- data/examples/readonly_proxy.rb +0 -25
- data/examples/simple_get.rb +0 -14
- data/examples/simple_publish.rb +0 -12
data/lib/mqtt/packet.rb
CHANGED
@@ -1,96 +1,162 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'mqtt'
|
4
|
-
|
5
1
|
module MQTT
|
6
2
|
|
7
3
|
# Class representing a MQTT Packet
|
8
4
|
# Performs binary encoding and decoding of headers
|
9
|
-
class Packet
|
10
|
-
attr_reader :dup
|
11
|
-
attr_reader :retain
|
12
|
-
attr_reader :qos
|
13
|
-
|
14
|
-
|
5
|
+
class MQTT::Packet
|
6
|
+
attr_reader :dup # Duplicate delivery flag
|
7
|
+
attr_reader :retain # Retain flag
|
8
|
+
attr_reader :qos # Quality of Service level
|
9
|
+
attr_reader :body_length # The length of the parsed packet body
|
10
|
+
|
11
|
+
# Deprecate this: Read in a packet from a socket
|
15
12
|
def self.read(socket)
|
16
13
|
# Read in the packet header and work out the class
|
17
14
|
header = read_byte(socket)
|
18
15
|
type_id = ((header & 0xF0) >> 4)
|
19
16
|
packet_class = MQTT::PACKET_TYPES[type_id]
|
20
|
-
|
17
|
+
|
21
18
|
# Create a new packet object
|
22
19
|
packet = packet_class.new(
|
23
20
|
:dup => ((header & 0x08) >> 3),
|
24
21
|
:qos => ((header & 0x06) >> 1),
|
25
22
|
:retain => ((header & 0x01) >> 0)
|
26
23
|
)
|
27
|
-
|
24
|
+
|
28
25
|
# Read in the packet length
|
29
|
-
multiplier = 1
|
30
|
-
|
26
|
+
multiplier = 1
|
27
|
+
body_length = 0
|
31
28
|
begin
|
32
29
|
digit = read_byte(socket)
|
33
|
-
|
30
|
+
body_length += ((digit & 0x7F) * multiplier)
|
34
31
|
multiplier *= 0x80
|
35
32
|
end while ((digit & 0x80) != 0x00)
|
36
33
|
# FIXME: only allow 4 bytes?
|
37
34
|
|
35
|
+
# Store the expected body length in the packet
|
36
|
+
packet.instance_variable_set('@body_length', body_length)
|
37
|
+
|
38
38
|
# Read in the packet body
|
39
|
-
packet.parse_body( socket.read(
|
39
|
+
packet.parse_body( socket.read(body_length) )
|
40
|
+
|
41
|
+
return packet
|
42
|
+
end
|
40
43
|
|
44
|
+
# Parse buffer into new packet object
|
45
|
+
def self.parse(buffer)
|
46
|
+
packet = parse_header(buffer)
|
47
|
+
packet.parse_body(buffer)
|
41
48
|
return packet
|
42
49
|
end
|
43
50
|
|
51
|
+
# Parse the header and create a new packet object of the correct type
|
52
|
+
# The header is removed from the buffer passed into this function
|
53
|
+
def self.parse_header(buffer)
|
54
|
+
# Work out the class
|
55
|
+
type_id = ((buffer[0] & 0xF0) >> 4)
|
56
|
+
packet_class = MQTT::PACKET_TYPES[type_id]
|
57
|
+
if packet_class.nil?
|
58
|
+
raise ProtocolException.new("Invalid packet type identifier: #{type_id}")
|
59
|
+
end
|
60
|
+
|
61
|
+
# Create a new packet object
|
62
|
+
packet = packet_class.new(
|
63
|
+
:dup => ((buffer[0] & 0x08) >> 3) == 0x01,
|
64
|
+
:qos => ((buffer[0] & 0x06) >> 1),
|
65
|
+
:retain => ((buffer[0] & 0x01) >> 0) == 0x01
|
66
|
+
)
|
67
|
+
|
68
|
+
# Parse the packet length
|
69
|
+
body_length = 0
|
70
|
+
multiplier = 1
|
71
|
+
pos = 1
|
72
|
+
begin
|
73
|
+
if buffer.length <= pos
|
74
|
+
raise ProtocolException.new("The packet length header is incomplete")
|
75
|
+
end
|
76
|
+
digit = buffer[pos]
|
77
|
+
body_length += ((digit & 0x7F) * multiplier)
|
78
|
+
multiplier *= 0x80
|
79
|
+
pos += 1
|
80
|
+
end while ((digit & 0x80) != 0x00) and pos <= 4
|
81
|
+
|
82
|
+
# Store the expected body length in the packet
|
83
|
+
packet.instance_variable_set('@body_length', body_length)
|
84
|
+
|
85
|
+
# Delete the variable length header from the raw packet passed in
|
86
|
+
buffer.slice!(0...pos)
|
87
|
+
|
88
|
+
return packet
|
89
|
+
end
|
90
|
+
|
91
|
+
|
44
92
|
# Create a new empty packet
|
45
93
|
def initialize(args={})
|
46
|
-
|
47
|
-
|
48
|
-
|
94
|
+
update_attributes({
|
95
|
+
:dup => false,
|
96
|
+
:qos => 0,
|
97
|
+
:retain => false,
|
98
|
+
:body_length => nil
|
99
|
+
}.merge(args))
|
49
100
|
end
|
50
|
-
|
101
|
+
|
102
|
+
def update_attributes(attr={})
|
103
|
+
attr.each_pair do |k,v|
|
104
|
+
send("#{k}=", v)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
51
108
|
# Get the identifer for this packet type
|
52
109
|
def type_id
|
53
110
|
index = MQTT::PACKET_TYPES.index(self.class)
|
54
|
-
|
111
|
+
if index.nil?
|
112
|
+
raise "Invalid packet type: #{self.class}"
|
113
|
+
end
|
55
114
|
return index
|
56
115
|
end
|
57
|
-
|
116
|
+
|
58
117
|
# Set the dup flag (true/false)
|
59
118
|
def dup=(arg)
|
60
119
|
if arg.kind_of?(Integer)
|
61
|
-
@dup = (arg != 0
|
120
|
+
@dup = (arg != 0)
|
62
121
|
else
|
63
122
|
@dup = arg
|
64
123
|
end
|
65
124
|
end
|
66
|
-
|
125
|
+
|
67
126
|
# Set the retain flag (true/false)
|
68
127
|
def retain=(arg)
|
69
128
|
if arg.kind_of?(Integer)
|
70
|
-
@retain = (arg != 0
|
129
|
+
@retain = (arg != 0)
|
71
130
|
else
|
72
131
|
@retain = arg
|
73
132
|
end
|
74
133
|
end
|
75
|
-
|
134
|
+
|
76
135
|
# Set the Quality of Service level (0/1/2)
|
77
136
|
def qos=(arg)
|
78
137
|
@qos = arg.to_i
|
79
138
|
end
|
80
139
|
|
140
|
+
# Set the length of the packet body
|
141
|
+
def body_length=(arg)
|
142
|
+
@body_length = arg.to_i
|
143
|
+
end
|
144
|
+
|
81
145
|
# Parse the body (variable header and payload) of a packet
|
82
146
|
def parse_body(buffer)
|
83
|
-
|
84
|
-
raise
|
147
|
+
if buffer.length != body_length
|
148
|
+
raise ProtocolException.new(
|
149
|
+
"Failed to parse packet - input buffer (#{buffer.length}) is not the same as the body length buffer (#{body_length})"
|
150
|
+
)
|
85
151
|
end
|
86
152
|
end
|
87
|
-
|
153
|
+
|
88
154
|
# Get serialisation of packet's body (variable header and payload)
|
89
155
|
def encode_body
|
90
156
|
'' # No body by default
|
91
157
|
end
|
92
158
|
|
93
|
-
|
159
|
+
|
94
160
|
# Serialise the packet
|
95
161
|
def to_s
|
96
162
|
# Encode the fixed header
|
@@ -100,19 +166,19 @@ module MQTT
|
|
100
166
|
((qos.to_i & 0x03) << 1) |
|
101
167
|
(retain ? 0x1 : 0x0)
|
102
168
|
]
|
103
|
-
|
169
|
+
|
104
170
|
# Get the packet's variable header and payload
|
105
171
|
body = self.encode_body
|
106
|
-
|
172
|
+
|
107
173
|
# Build up the body length field bytes
|
108
|
-
|
174
|
+
body_length = body.length
|
109
175
|
begin
|
110
|
-
digit = (
|
111
|
-
|
176
|
+
digit = (body_length % 128)
|
177
|
+
body_length = (body_length / 128)
|
112
178
|
# if there are more digits to encode, set the top bit of this digit
|
113
|
-
digit |= 0x80 if (
|
179
|
+
digit |= 0x80 if (body_length > 0)
|
114
180
|
header.push(digit)
|
115
|
-
end while (
|
181
|
+
end while (body_length > 0)
|
116
182
|
|
117
183
|
# Convert header to binary and add on body
|
118
184
|
header.pack('C*') + body
|
@@ -120,7 +186,7 @@ module MQTT
|
|
120
186
|
|
121
187
|
|
122
188
|
protected
|
123
|
-
|
189
|
+
|
124
190
|
# Encode an array of bytes and return them
|
125
191
|
def encode_bytes(*bytes)
|
126
192
|
bytes.pack('C*')
|
@@ -135,45 +201,47 @@ module MQTT
|
|
135
201
|
# (preceded by the length of the string)
|
136
202
|
def encode_string(str)
|
137
203
|
str = str.to_s unless str.is_a?(String)
|
138
|
-
encode_short(str.
|
204
|
+
encode_short(str.length) + str
|
139
205
|
end
|
140
|
-
|
206
|
+
|
141
207
|
# Remove a 16-bit unsigned integer from the front of buffer
|
142
208
|
def shift_short(buffer)
|
143
209
|
bytes = buffer.slice!(0..1)
|
144
210
|
bytes.unpack('n').first
|
145
211
|
end
|
146
|
-
|
212
|
+
|
147
213
|
# Remove one byte from the front of the string
|
148
214
|
def shift_byte(buffer)
|
149
215
|
buffer.slice!(0...1).unpack('C').first
|
150
216
|
end
|
151
|
-
|
217
|
+
|
152
218
|
# Remove n bytes from the front of buffer
|
153
219
|
def shift_data(buffer,bytes)
|
154
220
|
buffer.slice!(0...bytes)
|
155
221
|
end
|
156
|
-
|
222
|
+
|
157
223
|
# Remove string from the front of buffer
|
158
224
|
def shift_string(buffer)
|
159
225
|
len = shift_short(buffer)
|
160
226
|
shift_data(buffer,len)
|
161
227
|
end
|
162
228
|
|
163
|
-
|
229
|
+
|
164
230
|
private
|
165
|
-
|
231
|
+
|
166
232
|
# Read and unpack a single byte from a socket
|
167
233
|
def self.read_byte(socket)
|
168
234
|
byte = socket.read(1)
|
169
|
-
|
235
|
+
if byte.nil?
|
236
|
+
raise ProtocolException
|
237
|
+
end
|
170
238
|
byte.unpack('C').first
|
171
239
|
end
|
172
240
|
|
173
241
|
|
174
242
|
|
175
243
|
## PACKET SUBCLASSES ##
|
176
|
-
|
244
|
+
|
177
245
|
|
178
246
|
# Class representing an MQTT Publish message
|
179
247
|
class Publish < MQTT::Packet
|
@@ -181,26 +249,30 @@ module MQTT
|
|
181
249
|
attr_accessor :message_id
|
182
250
|
attr_accessor :payload
|
183
251
|
|
184
|
-
# Create a new
|
252
|
+
# Create a new Publish packet
|
185
253
|
def initialize(args={})
|
186
|
-
super(
|
187
|
-
|
188
|
-
|
189
|
-
|
254
|
+
super({
|
255
|
+
:topic => nil,
|
256
|
+
:message_id => 0,
|
257
|
+
:payload => ''
|
258
|
+
}.merge(args))
|
190
259
|
end
|
191
|
-
|
260
|
+
|
192
261
|
# Get serialisation of packet's body
|
193
262
|
def encode_body
|
194
263
|
body = ''
|
195
|
-
|
264
|
+
if @topic.nil?
|
265
|
+
raise "Invalid topic name when serialising packet"
|
266
|
+
end
|
196
267
|
body += encode_string(@topic)
|
197
268
|
body += encode_short(@message_id) unless qos == 0
|
198
269
|
body += payload.to_s
|
199
270
|
return body
|
200
271
|
end
|
201
|
-
|
272
|
+
|
202
273
|
# Parse the body (variable header and payload) of a Publish packet
|
203
274
|
def parse_body(buffer)
|
275
|
+
super(buffer)
|
204
276
|
@topic = shift_string(buffer)
|
205
277
|
@message_id = shift_short(buffer) unless qos == 0
|
206
278
|
@payload = buffer.dup
|
@@ -212,52 +284,88 @@ module MQTT
|
|
212
284
|
attr_accessor :protocol_name
|
213
285
|
attr_accessor :protocol_version
|
214
286
|
attr_accessor :client_id
|
215
|
-
attr_accessor :
|
287
|
+
attr_accessor :clean_session
|
216
288
|
attr_accessor :keep_alive
|
217
289
|
attr_accessor :will_topic
|
218
290
|
attr_accessor :will_qos
|
219
291
|
attr_accessor :will_retain
|
220
292
|
attr_accessor :will_payload
|
293
|
+
attr_accessor :username
|
294
|
+
attr_accessor :password
|
295
|
+
|
296
|
+
# OLD deprecated clean_start
|
297
|
+
alias :clean_start :clean_session
|
298
|
+
alias :clean_start= :clean_session=
|
221
299
|
|
222
300
|
# Create a new Client Connect packet
|
223
301
|
def initialize(args={})
|
224
|
-
super(
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
302
|
+
super({
|
303
|
+
:protocol_name => 'MQIsdp',
|
304
|
+
:protocol_version => 0x03,
|
305
|
+
:client_id => nil,
|
306
|
+
:clean_session => true,
|
307
|
+
:keep_alive => 10,
|
308
|
+
:will_topic => nil,
|
309
|
+
:will_qos => 0,
|
310
|
+
:will_retain => false,
|
311
|
+
:will_payload => '',
|
312
|
+
:username => nil,
|
313
|
+
:password => nil,
|
314
|
+
}.merge(args))
|
315
|
+
end
|
316
|
+
|
236
317
|
# Get serialisation of packet's body
|
237
318
|
def encode_body
|
238
319
|
body = ''
|
239
|
-
|
320
|
+
if @client_id.nil? or @client_id.length < 1 or @client_id.length > 23
|
321
|
+
raise "Invalid client identifier when serialising packet"
|
322
|
+
end
|
240
323
|
body += encode_string(@protocol_name)
|
241
324
|
body += encode_bytes(@protocol_version.to_i)
|
242
|
-
|
243
|
-
|
325
|
+
|
326
|
+
# Set the Connect flags
|
327
|
+
@connect_flags = 0
|
328
|
+
@connect_flags |= 0x02 if @clean_session
|
329
|
+
@connect_flags |= 0x04 unless @will_topic.nil?
|
330
|
+
@connect_flags |= ((@will_qos & 0x03) << 3)
|
331
|
+
@connect_flags |= 0x20 if @will_retain
|
332
|
+
@connect_flags |= 0x40 unless @password.nil?
|
333
|
+
@connect_flags |= 0x80 unless @username.nil?
|
334
|
+
body += encode_bytes(@connect_flags)
|
335
|
+
|
336
|
+
body += encode_short(@keep_alive)
|
244
337
|
body += encode_string(@client_id)
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
338
|
+
unless will_topic.nil?
|
339
|
+
body += encode_string(@will_topic)
|
340
|
+
body += encode_string(@will_payload)
|
341
|
+
end
|
342
|
+
body += encode_string(@username) unless @username.nil?
|
343
|
+
body += encode_string(@password) unless @password.nil?
|
250
344
|
return body
|
251
345
|
end
|
252
|
-
|
346
|
+
|
253
347
|
# Parse the body (variable header and payload) of a Connect packet
|
254
348
|
def parse_body(buffer)
|
349
|
+
super(buffer)
|
255
350
|
@protocol_name = shift_string(buffer)
|
256
351
|
@protocol_version = shift_byte(buffer)
|
257
|
-
|
352
|
+
@connect_flags = shift_byte(buffer)
|
353
|
+
@clean_session = ((@connect_flags & 0x02) >> 1) == 0x01
|
258
354
|
@keep_alive = shift_short(buffer)
|
259
355
|
@client_id = shift_string(buffer)
|
260
|
-
|
356
|
+
if ((@connect_flags & 0x04) >> 2) == 0x01
|
357
|
+
# Last Will and Testament
|
358
|
+
@will_qos = ((@connect_flags & 0x18) >> 3)
|
359
|
+
@will_retain = ((@connect_flags & 0x20) >> 5) == 0x01
|
360
|
+
@will_topic = shift_string(buffer)
|
361
|
+
@will_payload = shift_string(buffer)
|
362
|
+
end
|
363
|
+
if ((@connect_flags & 0x80) >> 7) == 0x01 and buffer.length > 0
|
364
|
+
@username = shift_string(buffer)
|
365
|
+
end
|
366
|
+
if ((@connect_flags & 0x40) >> 6) == 0x01 and buffer.length > 0
|
367
|
+
@password = shift_string(buffer)
|
368
|
+
end
|
261
369
|
end
|
262
370
|
end
|
263
371
|
|
@@ -267,10 +375,11 @@ module MQTT
|
|
267
375
|
|
268
376
|
# Create a new Client Connect packet
|
269
377
|
def initialize(args={})
|
270
|
-
super(
|
271
|
-
|
378
|
+
super({
|
379
|
+
:return_code => 0x00
|
380
|
+
}.merge(args))
|
272
381
|
end
|
273
|
-
|
382
|
+
|
274
383
|
# Get a string message corresponding to a return code
|
275
384
|
def return_msg
|
276
385
|
case return_code
|
@@ -282,11 +391,15 @@ module MQTT
|
|
282
391
|
"Connection refused: client identifier rejected"
|
283
392
|
when 0x03
|
284
393
|
"Connection refused: broker unavailable"
|
394
|
+
when 0x04
|
395
|
+
"Connection refused: bad user name or password"
|
396
|
+
when 0x05
|
397
|
+
"Connection refused: not authorised"
|
285
398
|
else
|
286
399
|
"Connection refused: error code #{return_code}"
|
287
400
|
end
|
288
401
|
end
|
289
|
-
|
402
|
+
|
290
403
|
# Get serialisation of packet's body
|
291
404
|
def encode_body
|
292
405
|
body = ''
|
@@ -294,111 +407,136 @@ module MQTT
|
|
294
407
|
body += encode_bytes(@return_code.to_i) # Return Code
|
295
408
|
return body
|
296
409
|
end
|
297
|
-
|
410
|
+
|
298
411
|
# Parse the body (variable header and payload) of a Connect Acknowledgment packet
|
299
412
|
def parse_body(buffer)
|
413
|
+
super(buffer)
|
300
414
|
unused = shift_byte(buffer)
|
301
415
|
@return_code = shift_byte(buffer)
|
302
|
-
|
416
|
+
unless buffer.empty?
|
417
|
+
raise ProtocolException.new("Extra bytes at end of Connect Acknowledgment packet")
|
418
|
+
end
|
419
|
+
end
|
303
420
|
end
|
304
421
|
|
305
422
|
# Class representing an MQTT Publish Acknowledgment packet
|
306
423
|
class Puback < MQTT::Packet
|
307
424
|
attr_accessor :message_id
|
308
|
-
|
309
|
-
# Create a new
|
425
|
+
|
426
|
+
# Create a new Publish Acknowledgment packet
|
310
427
|
def initialize(args={})
|
311
|
-
super(
|
312
|
-
|
428
|
+
super({
|
429
|
+
:message_id => 0
|
430
|
+
}.merge(args))
|
313
431
|
end
|
314
|
-
|
432
|
+
|
315
433
|
# Get serialisation of packet's body
|
316
434
|
def encode_body
|
317
435
|
encode_short(@message_id)
|
318
436
|
end
|
319
|
-
|
437
|
+
|
320
438
|
# Parse the body (variable header and payload) of a packet
|
321
439
|
def parse_body(buffer)
|
440
|
+
super(buffer)
|
322
441
|
@message_id = shift_short(buffer)
|
442
|
+
unless buffer.empty?
|
443
|
+
raise ProtocolException.new("Extra bytes at end of Publish Acknowledgment packet")
|
444
|
+
end
|
323
445
|
end
|
324
446
|
end
|
325
447
|
|
326
448
|
# Class representing an MQTT Publish Received packet
|
327
449
|
class Pubrec < MQTT::Packet
|
328
450
|
attr_accessor :message_id
|
329
|
-
|
330
|
-
# Create a new
|
451
|
+
|
452
|
+
# Create a new Publish Recieved packet
|
331
453
|
def initialize(args={})
|
332
|
-
super(
|
333
|
-
|
454
|
+
super({
|
455
|
+
:message_id => 0
|
456
|
+
}.merge(args))
|
334
457
|
end
|
335
|
-
|
458
|
+
|
336
459
|
# Get serialisation of packet's body
|
337
460
|
def encode_body
|
338
461
|
encode_short(@message_id)
|
339
462
|
end
|
340
|
-
|
463
|
+
|
341
464
|
# Parse the body (variable header and payload) of a packet
|
342
465
|
def parse_body(buffer)
|
466
|
+
super(buffer)
|
343
467
|
@message_id = shift_short(buffer)
|
468
|
+
unless buffer.empty?
|
469
|
+
raise ProtocolException.new("Extra bytes at end of Publish Received packet")
|
470
|
+
end
|
344
471
|
end
|
345
472
|
end
|
346
473
|
|
347
474
|
# Class representing an MQTT Publish Release packet
|
348
475
|
class Pubrel < MQTT::Packet
|
349
476
|
attr_accessor :message_id
|
350
|
-
|
351
|
-
# Create a new
|
477
|
+
|
478
|
+
# Create a new Publish Release packet
|
352
479
|
def initialize(args={})
|
353
|
-
super(
|
354
|
-
|
480
|
+
super({
|
481
|
+
:message_id => 0
|
482
|
+
}.merge(args))
|
355
483
|
end
|
356
|
-
|
484
|
+
|
357
485
|
# Get serialisation of packet's body
|
358
486
|
def encode_body
|
359
487
|
encode_short(@message_id)
|
360
488
|
end
|
361
|
-
|
489
|
+
|
362
490
|
# Parse the body (variable header and payload) of a packet
|
363
491
|
def parse_body(buffer)
|
492
|
+
super(buffer)
|
364
493
|
@message_id = shift_short(buffer)
|
494
|
+
unless buffer.empty?
|
495
|
+
raise ProtocolException.new("Extra bytes at end of Publish Release packet")
|
496
|
+
end
|
365
497
|
end
|
366
498
|
end
|
367
499
|
|
368
500
|
# Class representing an MQTT Publish Complete packet
|
369
501
|
class Pubcomp < MQTT::Packet
|
370
502
|
attr_accessor :message_id
|
371
|
-
|
372
|
-
# Create a new
|
503
|
+
|
504
|
+
# Create a new Publish Complete packet
|
373
505
|
def initialize(args={})
|
374
|
-
super(
|
375
|
-
|
506
|
+
super({
|
507
|
+
:message_id => 0
|
508
|
+
}.merge(args))
|
376
509
|
end
|
377
|
-
|
510
|
+
|
378
511
|
# Get serialisation of packet's body
|
379
512
|
def encode_body
|
380
513
|
encode_short(@message_id)
|
381
514
|
end
|
382
|
-
|
515
|
+
|
383
516
|
# Parse the body (variable header and payload) of a packet
|
384
517
|
def parse_body(buffer)
|
518
|
+
super(buffer)
|
385
519
|
@message_id = shift_short(buffer)
|
520
|
+
unless buffer.empty?
|
521
|
+
raise ProtocolException.new("Extra bytes at end of Publish Complete packet")
|
522
|
+
end
|
386
523
|
end
|
387
524
|
end
|
388
525
|
|
389
526
|
# Class representing an MQTT Client Subscribe packet
|
390
527
|
class Subscribe < MQTT::Packet
|
391
|
-
attr_reader :topics
|
392
528
|
attr_accessor :message_id
|
393
|
-
|
394
|
-
|
529
|
+
attr_reader :topics
|
530
|
+
|
531
|
+
# Create a new Subscribe packet
|
395
532
|
def initialize(args={})
|
396
|
-
super(
|
397
|
-
|
398
|
-
|
533
|
+
super({
|
534
|
+
:topics => [],
|
535
|
+
:message_id => 0
|
536
|
+
}.merge(args))
|
399
537
|
self.qos = 1 # Force a QOS of 1
|
400
538
|
end
|
401
|
-
|
539
|
+
|
402
540
|
# Set one or more topics for the Subscrible packet
|
403
541
|
# The topics parameter should be one of the following:
|
404
542
|
# * String: subscribe to one topic with QOS 0
|
@@ -418,9 +556,9 @@ module MQTT
|
|
418
556
|
else
|
419
557
|
input = [value]
|
420
558
|
end
|
421
|
-
|
559
|
+
|
422
560
|
@topics = []
|
423
|
-
while(input.
|
561
|
+
while(input.length>0)
|
424
562
|
item = input.shift
|
425
563
|
if item.is_a?(Hash)
|
426
564
|
# Convert hash into an ordered array of arrays
|
@@ -440,10 +578,12 @@ module MQTT
|
|
440
578
|
end
|
441
579
|
@topics
|
442
580
|
end
|
443
|
-
|
581
|
+
|
444
582
|
# Get serialisation of packet's body
|
445
583
|
def encode_body
|
446
|
-
|
584
|
+
if @topics.empty?
|
585
|
+
raise "no topics given when serialising packet"
|
586
|
+
end
|
447
587
|
body = encode_short(@message_id)
|
448
588
|
topics.each do |item|
|
449
589
|
body += encode_string(item[0])
|
@@ -451,12 +591,13 @@ module MQTT
|
|
451
591
|
end
|
452
592
|
return body
|
453
593
|
end
|
454
|
-
|
594
|
+
|
455
595
|
# Parse the body (variable header and payload) of a packet
|
456
596
|
def parse_body(buffer)
|
597
|
+
super(buffer)
|
457
598
|
@message_id = shift_short(buffer)
|
458
599
|
@topics = []
|
459
|
-
while(buffer.
|
600
|
+
while(buffer.length>0)
|
460
601
|
topic_name = shift_string(buffer)
|
461
602
|
topic_qos = shift_byte(buffer)
|
462
603
|
@topics << [topic_name,topic_qos]
|
@@ -468,31 +609,37 @@ module MQTT
|
|
468
609
|
class Suback < MQTT::Packet
|
469
610
|
attr_accessor :message_id
|
470
611
|
attr_reader :granted_qos
|
471
|
-
|
472
|
-
# Create a new
|
612
|
+
|
613
|
+
# Create a new Subscribe Acknowledgment packet
|
473
614
|
def initialize(args={})
|
474
|
-
super(
|
475
|
-
|
476
|
-
|
615
|
+
super({
|
616
|
+
:granted_qos => [],
|
617
|
+
:message_id => 0
|
618
|
+
}.merge(args))
|
477
619
|
end
|
478
|
-
|
620
|
+
|
479
621
|
def granted_qos=(value)
|
480
|
-
|
622
|
+
unless value.is_a?(Array)
|
623
|
+
raise "granted QOS should be an array of arrays"
|
624
|
+
end
|
481
625
|
@granted_qos = value
|
482
626
|
end
|
483
|
-
|
627
|
+
|
484
628
|
# Get serialisation of packet's body
|
485
629
|
def encode_body
|
486
|
-
|
630
|
+
if @granted_qos.empty?
|
631
|
+
raise "no granted QOS given when serialising packet"
|
632
|
+
end
|
487
633
|
body = encode_short(@message_id)
|
488
634
|
granted_qos.flatten.each { |qos| body += encode_bytes(qos) }
|
489
635
|
return body
|
490
636
|
end
|
491
|
-
|
637
|
+
|
492
638
|
# Parse the body (variable header and payload) of a packet
|
493
639
|
def parse_body(buffer)
|
640
|
+
super(buffer)
|
494
641
|
@message_id = shift_short(buffer)
|
495
|
-
while(buffer.
|
642
|
+
while(buffer.length>0)
|
496
643
|
@granted_qos << [shift_byte(buffer),shift_byte(buffer)]
|
497
644
|
end
|
498
645
|
end
|
@@ -502,15 +649,16 @@ module MQTT
|
|
502
649
|
class Unsubscribe < MQTT::Packet
|
503
650
|
attr_reader :topics
|
504
651
|
attr_accessor :message_id
|
505
|
-
|
506
|
-
# Create a new Unsubscribe
|
652
|
+
|
653
|
+
# Create a new Unsubscribe packet
|
507
654
|
def initialize(args={})
|
508
|
-
super(
|
509
|
-
|
510
|
-
|
655
|
+
super({
|
656
|
+
:topics => [],
|
657
|
+
:message_id => 0
|
658
|
+
}.merge(args))
|
511
659
|
self.qos = 1 # Force a QOS of 1
|
512
660
|
end
|
513
|
-
|
661
|
+
|
514
662
|
def topics=(value)
|
515
663
|
if value.is_a?(Array)
|
516
664
|
@topics = value
|
@@ -518,19 +666,22 @@ module MQTT
|
|
518
666
|
@topics = [value]
|
519
667
|
end
|
520
668
|
end
|
521
|
-
|
669
|
+
|
522
670
|
# Get serialisation of packet's body
|
523
671
|
def encode_body
|
524
|
-
|
672
|
+
if @topics.empty?
|
673
|
+
raise "no topics given when serialising packet"
|
674
|
+
end
|
525
675
|
body = encode_short(@message_id)
|
526
676
|
topics.each { |topic| body += encode_string(topic) }
|
527
677
|
return body
|
528
678
|
end
|
529
|
-
|
679
|
+
|
530
680
|
# Parse the body (variable header and payload) of a packet
|
531
681
|
def parse_body(buffer)
|
682
|
+
super(buffer)
|
532
683
|
@message_id = shift_short(buffer)
|
533
|
-
while(buffer.
|
684
|
+
while(buffer.length>0)
|
534
685
|
@topics << shift_string(buffer)
|
535
686
|
end
|
536
687
|
end
|
@@ -539,21 +690,26 @@ module MQTT
|
|
539
690
|
# Class representing an MQTT Unsubscribe Acknowledgment packet
|
540
691
|
class Unsuback < MQTT::Packet
|
541
692
|
attr_accessor :message_id
|
542
|
-
|
693
|
+
|
543
694
|
# Create a new Unsubscribe Acknowledgment packet
|
544
695
|
def initialize(args={})
|
545
|
-
super(
|
546
|
-
|
696
|
+
super({
|
697
|
+
:message_id => 0
|
698
|
+
}.merge(args))
|
547
699
|
end
|
548
|
-
|
700
|
+
|
549
701
|
# Get serialisation of packet's body
|
550
702
|
def encode_body
|
551
703
|
encode_short(@message_id)
|
552
704
|
end
|
553
|
-
|
705
|
+
|
554
706
|
# Parse the body (variable header and payload) of a packet
|
555
707
|
def parse_body(buffer)
|
708
|
+
super(buffer)
|
556
709
|
@message_id = shift_short(buffer)
|
710
|
+
unless buffer.empty?
|
711
|
+
raise ProtocolException.new("Extra bytes at end of Unsubscribe Acknowledgment packet")
|
712
|
+
end
|
557
713
|
end
|
558
714
|
end
|
559
715
|
|
@@ -563,6 +719,14 @@ module MQTT
|
|
563
719
|
def initialize(args={})
|
564
720
|
super(args)
|
565
721
|
end
|
722
|
+
|
723
|
+
# Check the body
|
724
|
+
def parse_body(buffer)
|
725
|
+
super(buffer)
|
726
|
+
unless buffer.empty?
|
727
|
+
raise ProtocolException.new("Extra bytes at end of Ping Request packet")
|
728
|
+
end
|
729
|
+
end
|
566
730
|
end
|
567
731
|
|
568
732
|
# Class representing an MQTT Ping Response packet
|
@@ -571,6 +735,14 @@ module MQTT
|
|
571
735
|
def initialize(args={})
|
572
736
|
super(args)
|
573
737
|
end
|
738
|
+
|
739
|
+
# Check the body
|
740
|
+
def parse_body(buffer)
|
741
|
+
super(buffer)
|
742
|
+
unless buffer.empty?
|
743
|
+
raise ProtocolException.new("Extra bytes at end of Ping Response packet")
|
744
|
+
end
|
745
|
+
end
|
574
746
|
end
|
575
747
|
|
576
748
|
# Class representing an MQTT Client Disconnect packet
|
@@ -579,8 +751,16 @@ module MQTT
|
|
579
751
|
def initialize(args={})
|
580
752
|
super(args)
|
581
753
|
end
|
754
|
+
|
755
|
+
# Check the body
|
756
|
+
def parse_body(buffer)
|
757
|
+
super(buffer)
|
758
|
+
unless buffer.empty?
|
759
|
+
raise ProtocolException.new("Extra bytes at end of Disconnect packet")
|
760
|
+
end
|
761
|
+
end
|
582
762
|
end
|
583
|
-
|
763
|
+
|
584
764
|
end
|
585
765
|
|
586
766
|
|
@@ -603,5 +783,5 @@ module MQTT
|
|
603
783
|
MQTT::Packet::Disconnect,
|
604
784
|
nil
|
605
785
|
]
|
606
|
-
|
786
|
+
|
607
787
|
end
|