mqtt 0.3.1 → 0.4.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.
- checksums.yaml +15 -0
- data/NEWS.md +13 -0
- data/README.md +99 -49
- data/lib/mqtt.rb +14 -1
- data/lib/mqtt/client.rb +93 -53
- data/lib/mqtt/packet.rb +6 -6
- data/lib/mqtt/patches/string_encoding.rb +4 -0
- data/lib/mqtt/sn/packet.rb +763 -0
- data/lib/mqtt/version.rb +1 -1
- data/spec/mqtt_client_spec.rb +147 -51
- data/spec/mqtt_packet_spec.rb +67 -68
- data/spec/mqtt_sn_packet_spec.rb +1721 -0
- data/spec/zz_client_integration_spec.rb +22 -1
- metadata +22 -31
data/lib/mqtt/packet.rb
CHANGED
@@ -368,7 +368,7 @@ module MQTT
|
|
368
368
|
end
|
369
369
|
body += encode_string(@topic)
|
370
370
|
body += encode_short(@id) unless qos == 0
|
371
|
-
body += payload.to_s.force_encoding('ASCII-8BIT')
|
371
|
+
body += payload.to_s.dup.force_encoding('ASCII-8BIT')
|
372
372
|
return body
|
373
373
|
end
|
374
374
|
|
@@ -780,9 +780,9 @@ module MQTT
|
|
780
780
|
|
781
781
|
# Set one or more topic filters for the Subscribe packet
|
782
782
|
# The topics parameter should be one of the following:
|
783
|
-
# * String: subscribe to one topic with
|
784
|
-
# * Array: subscribe to multiple topics with
|
785
|
-
# * Hash: subscribe to multiple topics where the key is the topic and the value is the
|
783
|
+
# * String: subscribe to one topic with QoS 0
|
784
|
+
# * Array: subscribe to multiple topics with QoS 0
|
785
|
+
# * Hash: subscribe to multiple topics where the key is the topic and the value is the QoS level
|
786
786
|
#
|
787
787
|
# For example:
|
788
788
|
# packet.topics = 'a/b'
|
@@ -877,7 +877,7 @@ module MQTT
|
|
877
877
|
super(ATTR_DEFAULTS.merge(args))
|
878
878
|
end
|
879
879
|
|
880
|
-
# Set the granted
|
880
|
+
# Set the granted QoS value for each of the topics that were subscribed to
|
881
881
|
# Can either be an integer or an array or integers.
|
882
882
|
def return_codes=(value)
|
883
883
|
if value.is_a?(Array)
|
@@ -892,7 +892,7 @@ module MQTT
|
|
892
892
|
# Get serialisation of packet's body
|
893
893
|
def encode_body
|
894
894
|
if @return_codes.empty?
|
895
|
-
raise "no granted
|
895
|
+
raise "no granted QoS given when serialising packet"
|
896
896
|
end
|
897
897
|
body = encode_short(@id)
|
898
898
|
return_codes.each { |qos| body += encode_bytes(qos) }
|
@@ -0,0 +1,763 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
3
|
+
module MQTT::SN
|
4
|
+
|
5
|
+
# Class representing a MQTT::SN Packet
|
6
|
+
# Performs binary encoding and decoding of headers
|
7
|
+
class Packet
|
8
|
+
attr_accessor :duplicate # Duplicate delivery flag
|
9
|
+
attr_accessor :qos # Quality of Service level
|
10
|
+
attr_accessor :retain # Retain flag
|
11
|
+
attr_accessor :request_will # Request that gateway prompts for Will
|
12
|
+
attr_accessor :clean_session # When true, subscriptions are deleted after disconnect
|
13
|
+
attr_accessor :topic_id_type # One of :normal, :predefined or :short
|
14
|
+
|
15
|
+
DEFAULTS = {}
|
16
|
+
|
17
|
+
# Parse buffer into new packet object
|
18
|
+
def self.parse(buffer)
|
19
|
+
# Parse the fixed header (length and type)
|
20
|
+
length,type_id,body = buffer.unpack('CCa*')
|
21
|
+
if length == 1
|
22
|
+
length,type_id,body = buffer.unpack('xnCa*')
|
23
|
+
end
|
24
|
+
|
25
|
+
# Double-check the length
|
26
|
+
if buffer.length != length
|
27
|
+
raise ProtocolException.new("Length of packet is not the same as the length header")
|
28
|
+
end
|
29
|
+
|
30
|
+
packet_class = PACKET_TYPES[type_id]
|
31
|
+
if packet_class.nil?
|
32
|
+
raise ProtocolException.new("Invalid packet type identifier: #{type_id}")
|
33
|
+
end
|
34
|
+
|
35
|
+
# Create a new packet object
|
36
|
+
packet = packet_class.new
|
37
|
+
packet.parse_body(body)
|
38
|
+
|
39
|
+
return packet
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create a new empty packet
|
43
|
+
def initialize(args={})
|
44
|
+
update_attributes(self.class::DEFAULTS.merge(args))
|
45
|
+
end
|
46
|
+
|
47
|
+
def update_attributes(attr={})
|
48
|
+
attr.each_pair do |k,v|
|
49
|
+
send("#{k}=", v)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Get the identifer for this packet type
|
54
|
+
def type_id
|
55
|
+
PACKET_TYPES.each_pair do |key, value|
|
56
|
+
return key if self.class == value
|
57
|
+
end
|
58
|
+
raise "Invalid packet type: #{self.class}"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Serialise the packet
|
62
|
+
def to_s
|
63
|
+
# Get the packet's variable header and payload
|
64
|
+
body = self.encode_body
|
65
|
+
|
66
|
+
# Build up the body length field bytes
|
67
|
+
body_length = body.length
|
68
|
+
if body_length > 65531
|
69
|
+
raise "MQTT-SN Packet is too big, maximum packet body size is 65531"
|
70
|
+
elsif body_length > 253
|
71
|
+
[0x01, body_length + 4, type_id].pack('CnC') + body
|
72
|
+
else
|
73
|
+
[body_length + 2, type_id].pack('CC') + body
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_body(buffer)
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
def parse_flags(flags)
|
83
|
+
self.duplicate = ((flags & 0x80) >> 7) == 0x01
|
84
|
+
self.qos = (flags & 0x60) >> 5
|
85
|
+
self.qos = -1 if self.qos == 3
|
86
|
+
self.retain = ((flags & 0x10) >> 4) == 0x01
|
87
|
+
self.request_will = ((flags & 0x08) >> 3) == 0x01
|
88
|
+
self.clean_session = ((flags & 0x04) >> 2) == 0x01
|
89
|
+
case (flags & 0x03)
|
90
|
+
when 0x0
|
91
|
+
self.topic_id_type = :normal
|
92
|
+
when 0x1
|
93
|
+
self.topic_id_type = :predefined
|
94
|
+
when 0x2
|
95
|
+
self.topic_id_type = :short
|
96
|
+
else
|
97
|
+
self.topic_id_type = nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get serialisation of packet's body (variable header and payload)
|
102
|
+
def encode_body
|
103
|
+
'' # No body by default
|
104
|
+
end
|
105
|
+
|
106
|
+
def encode_flags
|
107
|
+
flags = 0x00
|
108
|
+
flags += 0x80 if duplicate
|
109
|
+
case qos
|
110
|
+
when -1
|
111
|
+
flags += 0x60
|
112
|
+
when 1
|
113
|
+
flags += 0x20
|
114
|
+
when 2
|
115
|
+
flags += 0x40
|
116
|
+
end
|
117
|
+
flags += 0x10 if retain
|
118
|
+
flags += 0x08 if request_will
|
119
|
+
flags += 0x04 if clean_session
|
120
|
+
case topic_id_type
|
121
|
+
when :normal
|
122
|
+
flags += 0x0
|
123
|
+
when :predefined
|
124
|
+
flags += 0x1
|
125
|
+
when :short
|
126
|
+
flags += 0x2
|
127
|
+
end
|
128
|
+
return flags
|
129
|
+
end
|
130
|
+
|
131
|
+
def encode_topic_id
|
132
|
+
if topic_id_type == :short
|
133
|
+
unless topic_id.is_a?(String)
|
134
|
+
raise "topic_id must be an String for type #{topic_id_type}"
|
135
|
+
end
|
136
|
+
(topic_id[0].ord << 8) + topic_id[1].ord
|
137
|
+
else
|
138
|
+
unless topic_id.is_a?(Integer)
|
139
|
+
raise "topic_id must be an Integer for type #{topic_id_type}"
|
140
|
+
end
|
141
|
+
topic_id
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def parse_topic_id(topic_id)
|
146
|
+
if topic_id_type == :short
|
147
|
+
int = topic_id.to_i
|
148
|
+
self.topic_id = [(int >> 8) & 0xFF, int & 0xFF].pack('CC')
|
149
|
+
else
|
150
|
+
self.topic_id = topic_id
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Used where a field can either be a Topic Id or a Topic Name
|
155
|
+
# (the Subscribe and Unsubscribe packet types)
|
156
|
+
def encode_topic
|
157
|
+
case topic_id_type
|
158
|
+
when :normal
|
159
|
+
topic_name
|
160
|
+
when :short
|
161
|
+
unless topic_name.nil?
|
162
|
+
topic_name
|
163
|
+
else
|
164
|
+
topic_id
|
165
|
+
end
|
166
|
+
when :predefined
|
167
|
+
[topic_id].pack('n')
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Used where a field can either be a Topic Id or a Topic Name
|
172
|
+
# (the Subscribe and Unsubscribe packet types)
|
173
|
+
def parse_topic(topic)
|
174
|
+
case topic_id_type
|
175
|
+
when :normal
|
176
|
+
self.topic_name = topic
|
177
|
+
when :short
|
178
|
+
self.topic_name = topic
|
179
|
+
self.topic_id = topic
|
180
|
+
when :predefined
|
181
|
+
self.topic_id = topic.unpack('n').first
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
class Advertise < Packet
|
186
|
+
attr_accessor :gateway_id
|
187
|
+
attr_accessor :duration
|
188
|
+
|
189
|
+
DEFAULTS = {
|
190
|
+
:gateway_id => 0x00,
|
191
|
+
:duration => 0
|
192
|
+
}
|
193
|
+
|
194
|
+
def encode_body
|
195
|
+
[gateway_id, duration].pack('Cn')
|
196
|
+
end
|
197
|
+
|
198
|
+
def parse_body(buffer)
|
199
|
+
self.gateway_id, self.duration = buffer.unpack('Cn')
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class Searchgw < Packet
|
204
|
+
attr_accessor :radius
|
205
|
+
DEFAULTS = {
|
206
|
+
:radius => 1
|
207
|
+
}
|
208
|
+
|
209
|
+
def encode_body
|
210
|
+
[radius].pack('C')
|
211
|
+
end
|
212
|
+
|
213
|
+
def parse_body(buffer)
|
214
|
+
self.radius, _ignore = buffer.unpack('C')
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
class Gwinfo < Packet
|
219
|
+
attr_accessor :gateway_id
|
220
|
+
attr_accessor :gateway_address
|
221
|
+
DEFAULTS = {
|
222
|
+
:gateway_id => 0,
|
223
|
+
:gateway_address => nil
|
224
|
+
}
|
225
|
+
|
226
|
+
def encode_body
|
227
|
+
[gateway_id,gateway_address].pack('Ca*')
|
228
|
+
end
|
229
|
+
|
230
|
+
def parse_body(buffer)
|
231
|
+
if buffer.length > 1
|
232
|
+
self.gateway_id, self.gateway_address = buffer.unpack('Ca*')
|
233
|
+
else
|
234
|
+
self.gateway_id, _ignore = buffer.unpack('C')
|
235
|
+
self.gateway_address = nil
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
class Connect < Packet
|
241
|
+
attr_accessor :keep_alive
|
242
|
+
attr_accessor :client_id
|
243
|
+
|
244
|
+
DEFAULTS = {
|
245
|
+
:request_will => false,
|
246
|
+
:clean_session => true,
|
247
|
+
:keep_alive => 15
|
248
|
+
}
|
249
|
+
|
250
|
+
# Get serialisation of packet's body
|
251
|
+
def encode_body
|
252
|
+
if @client_id.nil? or @client_id.length < 1 or @client_id.length > 23
|
253
|
+
raise "Invalid client identifier when serialising packet"
|
254
|
+
end
|
255
|
+
|
256
|
+
[encode_flags, 0x01, keep_alive, client_id].pack('CCna*')
|
257
|
+
end
|
258
|
+
|
259
|
+
def parse_body(buffer)
|
260
|
+
flags, protocol_id, self.keep_alive, self.client_id = buffer.unpack('CCna*')
|
261
|
+
|
262
|
+
if protocol_id != 0x01
|
263
|
+
raise ProtocolException.new("Unsupported protocol ID number: #{protocol_id}")
|
264
|
+
end
|
265
|
+
|
266
|
+
parse_flags(flags)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
class Connack < Packet
|
271
|
+
attr_accessor :return_code
|
272
|
+
|
273
|
+
# Get a string message corresponding to a return code
|
274
|
+
def return_msg
|
275
|
+
case return_code
|
276
|
+
when 0x00
|
277
|
+
"Accepted"
|
278
|
+
when 0x01
|
279
|
+
"Rejected: congestion"
|
280
|
+
when 0x02
|
281
|
+
"Rejected: invalid topic ID"
|
282
|
+
when 0x03
|
283
|
+
"Rejected: not supported"
|
284
|
+
else
|
285
|
+
"Rejected: error code #{return_code}"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def encode_body
|
290
|
+
unless return_code.is_a?(Integer)
|
291
|
+
raise "return_code must be an Integer"
|
292
|
+
end
|
293
|
+
|
294
|
+
[return_code].pack('C')
|
295
|
+
end
|
296
|
+
|
297
|
+
def parse_body(buffer)
|
298
|
+
self.return_code = buffer.unpack('C')[0]
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
class Willtopicreq < Packet
|
303
|
+
# No attributes
|
304
|
+
end
|
305
|
+
|
306
|
+
class Willtopic < Packet
|
307
|
+
attr_accessor :topic_name
|
308
|
+
|
309
|
+
DEFAULTS = {
|
310
|
+
:qos => 0,
|
311
|
+
:retain => false,
|
312
|
+
:topic_name => nil
|
313
|
+
}
|
314
|
+
|
315
|
+
def encode_body
|
316
|
+
if topic_name.nil? or topic_name.empty?
|
317
|
+
''
|
318
|
+
else
|
319
|
+
[encode_flags, topic_name].pack('Ca*')
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def parse_body(buffer)
|
324
|
+
if buffer.length > 1
|
325
|
+
flags, self.topic_name = buffer.unpack('Ca*')
|
326
|
+
else
|
327
|
+
flags, _ignore = buffer.unpack('C')
|
328
|
+
self.topic_name = nil
|
329
|
+
end
|
330
|
+
parse_flags(flags)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
class Willmsgreq < Packet
|
335
|
+
# No attributes
|
336
|
+
end
|
337
|
+
|
338
|
+
class Willmsg < Packet
|
339
|
+
attr_accessor :data
|
340
|
+
|
341
|
+
def encode_body
|
342
|
+
data
|
343
|
+
end
|
344
|
+
|
345
|
+
def parse_body(buffer)
|
346
|
+
self.data = buffer
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
class Register < Packet
|
351
|
+
attr_accessor :id
|
352
|
+
attr_accessor :topic_id
|
353
|
+
attr_accessor :topic_name
|
354
|
+
|
355
|
+
DEFAULTS = {
|
356
|
+
:id => 0x00,
|
357
|
+
:topic_id_type => :normal
|
358
|
+
}
|
359
|
+
|
360
|
+
def encode_body
|
361
|
+
unless id.is_a?(Integer)
|
362
|
+
raise "id must be an Integer"
|
363
|
+
end
|
364
|
+
|
365
|
+
unless topic_id.is_a?(Integer)
|
366
|
+
raise "topic_id must be an Integer"
|
367
|
+
end
|
368
|
+
|
369
|
+
[topic_id, id, topic_name].pack('nna*')
|
370
|
+
end
|
371
|
+
|
372
|
+
def parse_body(buffer)
|
373
|
+
self.topic_id, self.id, self.topic_name = buffer.unpack('nna*')
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
class Regack < Packet
|
378
|
+
attr_accessor :id
|
379
|
+
attr_accessor :topic_id
|
380
|
+
attr_accessor :return_code
|
381
|
+
|
382
|
+
DEFAULTS = {
|
383
|
+
:id => 0x00,
|
384
|
+
:topic_id => 0x00,
|
385
|
+
:topic_id_type => :normal
|
386
|
+
}
|
387
|
+
|
388
|
+
def encode_body
|
389
|
+
unless id.is_a?(Integer)
|
390
|
+
raise "id must be an Integer"
|
391
|
+
end
|
392
|
+
|
393
|
+
unless topic_id.is_a?(Integer)
|
394
|
+
raise "topic_id must be an Integer"
|
395
|
+
end
|
396
|
+
|
397
|
+
[topic_id, id, return_code].pack('nnC')
|
398
|
+
end
|
399
|
+
|
400
|
+
def parse_body(buffer)
|
401
|
+
self.topic_id, self.id, self.return_code = buffer.unpack('nnC')
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
class Publish < Packet
|
406
|
+
attr_accessor :topic_id
|
407
|
+
attr_accessor :id
|
408
|
+
attr_accessor :data
|
409
|
+
|
410
|
+
DEFAULTS = {
|
411
|
+
:id => 0x00,
|
412
|
+
:duplicate => false,
|
413
|
+
:qos => 0,
|
414
|
+
:retain => false,
|
415
|
+
:topic_id_type => :normal
|
416
|
+
}
|
417
|
+
|
418
|
+
def encode_body
|
419
|
+
unless id.is_a?(Integer)
|
420
|
+
raise "id must be an Integer"
|
421
|
+
end
|
422
|
+
|
423
|
+
[encode_flags, encode_topic_id, id, data].pack('Cnna*')
|
424
|
+
end
|
425
|
+
|
426
|
+
def parse_body(buffer)
|
427
|
+
flags, topic_id, self.id, self.data = buffer.unpack('Cnna*')
|
428
|
+
parse_flags(flags)
|
429
|
+
parse_topic_id(topic_id)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
class Puback < Packet
|
434
|
+
attr_accessor :topic_id
|
435
|
+
attr_accessor :id
|
436
|
+
attr_accessor :return_code
|
437
|
+
|
438
|
+
DEFAULTS = {
|
439
|
+
:id => 0x00,
|
440
|
+
:topic_id => nil,
|
441
|
+
:return_code => 0x00,
|
442
|
+
}
|
443
|
+
|
444
|
+
def encode_body
|
445
|
+
unless id.is_a?(Integer)
|
446
|
+
raise "id must be an Integer"
|
447
|
+
end
|
448
|
+
|
449
|
+
unless topic_id.is_a?(Integer)
|
450
|
+
raise "topic_id must be an Integer"
|
451
|
+
end
|
452
|
+
|
453
|
+
[topic_id, id, return_code].pack('nnC')
|
454
|
+
end
|
455
|
+
|
456
|
+
def parse_body(buffer)
|
457
|
+
self.topic_id, self.id, self.return_code = buffer.unpack('nnC')
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
class Pubcomp < Packet
|
462
|
+
attr_accessor :id
|
463
|
+
|
464
|
+
DEFAULTS = {
|
465
|
+
:id => 0x00
|
466
|
+
}
|
467
|
+
|
468
|
+
def encode_body
|
469
|
+
unless id.is_a?(Integer)
|
470
|
+
raise "id must be an Integer"
|
471
|
+
end
|
472
|
+
|
473
|
+
[id].pack('n')
|
474
|
+
end
|
475
|
+
|
476
|
+
def parse_body(buffer)
|
477
|
+
self.id, _ignore = buffer.unpack('n')
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
class Pubrec < Packet
|
482
|
+
attr_accessor :id
|
483
|
+
|
484
|
+
DEFAULTS = {
|
485
|
+
:id => 0x00
|
486
|
+
}
|
487
|
+
|
488
|
+
def encode_body
|
489
|
+
unless id.is_a?(Integer)
|
490
|
+
raise "id must be an Integer"
|
491
|
+
end
|
492
|
+
|
493
|
+
[id].pack('n')
|
494
|
+
end
|
495
|
+
|
496
|
+
def parse_body(buffer)
|
497
|
+
self.id, _ignore = buffer.unpack('n')
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
class Pubrel < Packet
|
502
|
+
attr_accessor :id
|
503
|
+
|
504
|
+
DEFAULTS = {
|
505
|
+
:id => 0x00
|
506
|
+
}
|
507
|
+
|
508
|
+
def encode_body
|
509
|
+
unless id.is_a?(Integer)
|
510
|
+
raise "id must be an Integer"
|
511
|
+
end
|
512
|
+
|
513
|
+
[id].pack('n')
|
514
|
+
end
|
515
|
+
|
516
|
+
def parse_body(buffer)
|
517
|
+
self.id, _ignore = buffer.unpack('n')
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
class Subscribe < Packet
|
522
|
+
attr_accessor :id
|
523
|
+
attr_accessor :topic_id
|
524
|
+
attr_accessor :topic_name
|
525
|
+
|
526
|
+
DEFAULTS = {
|
527
|
+
:id => 0x00,
|
528
|
+
:topic_id_type => :normal
|
529
|
+
}
|
530
|
+
|
531
|
+
def encode_body
|
532
|
+
unless id.is_a?(Integer)
|
533
|
+
raise "id must be an Integer"
|
534
|
+
end
|
535
|
+
|
536
|
+
[encode_flags, id, encode_topic].pack('Cna*')
|
537
|
+
end
|
538
|
+
|
539
|
+
def parse_body(buffer)
|
540
|
+
flags, self.id, topic = buffer.unpack('Cna*')
|
541
|
+
parse_flags(flags)
|
542
|
+
parse_topic(topic)
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
class Suback < Packet
|
547
|
+
attr_accessor :id
|
548
|
+
attr_accessor :topic_id
|
549
|
+
attr_accessor :return_code
|
550
|
+
|
551
|
+
DEFAULTS = {
|
552
|
+
:qos => 0,
|
553
|
+
:id => 0x00,
|
554
|
+
:topic_id => 0x00,
|
555
|
+
:topic_id_type => :normal
|
556
|
+
}
|
557
|
+
|
558
|
+
def encode_body
|
559
|
+
unless id.is_a?(Integer)
|
560
|
+
raise "id must be an Integer"
|
561
|
+
end
|
562
|
+
|
563
|
+
[encode_flags, encode_topic_id, id, return_code].pack('CnnC')
|
564
|
+
end
|
565
|
+
|
566
|
+
def parse_body(buffer)
|
567
|
+
flags, topic_id, self.id, self.return_code = buffer.unpack('CnnC')
|
568
|
+
parse_flags(flags)
|
569
|
+
parse_topic_id(topic_id)
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
class Unsubscribe < Packet
|
574
|
+
attr_accessor :id
|
575
|
+
attr_accessor :topic_id
|
576
|
+
attr_accessor :topic_name
|
577
|
+
|
578
|
+
DEFAULTS = {
|
579
|
+
:id => 0x00,
|
580
|
+
:topic_id_type => :normal
|
581
|
+
}
|
582
|
+
|
583
|
+
def encode_body
|
584
|
+
unless id.is_a?(Integer)
|
585
|
+
raise "id must be an Integer"
|
586
|
+
end
|
587
|
+
|
588
|
+
[encode_flags, id, encode_topic].pack('Cna*')
|
589
|
+
end
|
590
|
+
|
591
|
+
def parse_body(buffer)
|
592
|
+
flags, self.id, topic = buffer.unpack('Cna*')
|
593
|
+
parse_flags(flags)
|
594
|
+
parse_topic(topic)
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
class Unsuback < Packet
|
599
|
+
attr_accessor :id
|
600
|
+
|
601
|
+
DEFAULTS = {
|
602
|
+
:id => 0x00,
|
603
|
+
}
|
604
|
+
|
605
|
+
def encode_body
|
606
|
+
unless id.is_a?(Integer)
|
607
|
+
raise "id must be an Integer"
|
608
|
+
end
|
609
|
+
|
610
|
+
[id].pack('n')
|
611
|
+
end
|
612
|
+
|
613
|
+
def parse_body(buffer)
|
614
|
+
self.id = buffer.unpack('n').first
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
class Pingreq < Packet
|
619
|
+
# No attributes
|
620
|
+
end
|
621
|
+
|
622
|
+
class Pingresp < Packet
|
623
|
+
# No attributes
|
624
|
+
end
|
625
|
+
|
626
|
+
class Disconnect < Packet
|
627
|
+
attr_accessor :duration
|
628
|
+
|
629
|
+
DEFAULTS = {
|
630
|
+
:duration => nil
|
631
|
+
}
|
632
|
+
|
633
|
+
def encode_body
|
634
|
+
if duration.nil? or duration == 0
|
635
|
+
''
|
636
|
+
else
|
637
|
+
[duration].pack('n')
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
def parse_body(buffer)
|
642
|
+
if buffer.length == 2
|
643
|
+
self.duration = buffer.unpack('n').first
|
644
|
+
else
|
645
|
+
self.duration = nil
|
646
|
+
end
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
class Willtopicupd < Packet
|
651
|
+
attr_accessor :topic_name
|
652
|
+
|
653
|
+
DEFAULTS = {
|
654
|
+
:qos => 0,
|
655
|
+
:retain => false,
|
656
|
+
:topic_name => nil
|
657
|
+
}
|
658
|
+
|
659
|
+
def encode_body
|
660
|
+
if topic_name.nil? or topic_name.empty?
|
661
|
+
''
|
662
|
+
else
|
663
|
+
[encode_flags, topic_name].pack('Ca*')
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
def parse_body(buffer)
|
668
|
+
if buffer.length > 1
|
669
|
+
flags, self.topic_name = buffer.unpack('Ca*')
|
670
|
+
parse_flags(flags)
|
671
|
+
else
|
672
|
+
self.topic_name = nil
|
673
|
+
end
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
class Willtopicresp < Packet
|
678
|
+
attr_accessor :return_code
|
679
|
+
|
680
|
+
DEFAULTS = {
|
681
|
+
:return_code => 0x00
|
682
|
+
}
|
683
|
+
|
684
|
+
def encode_body
|
685
|
+
unless return_code.is_a?(Integer)
|
686
|
+
raise "return_code must be an Integer"
|
687
|
+
end
|
688
|
+
|
689
|
+
[return_code].pack('C')
|
690
|
+
end
|
691
|
+
|
692
|
+
def parse_body(buffer)
|
693
|
+
self.return_code, _ignore = buffer.unpack('C')
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
class Willmsgupd < Packet
|
698
|
+
attr_accessor :data
|
699
|
+
|
700
|
+
def encode_body
|
701
|
+
data
|
702
|
+
end
|
703
|
+
|
704
|
+
def parse_body(buffer)
|
705
|
+
self.data = buffer
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
class Willmsgresp < Packet
|
710
|
+
attr_accessor :return_code
|
711
|
+
|
712
|
+
DEFAULTS = {
|
713
|
+
:return_code => 0x00
|
714
|
+
}
|
715
|
+
|
716
|
+
def encode_body
|
717
|
+
unless return_code.is_a?(Integer)
|
718
|
+
raise "return_code must be an Integer"
|
719
|
+
end
|
720
|
+
|
721
|
+
[return_code].pack('C')
|
722
|
+
end
|
723
|
+
|
724
|
+
def parse_body(buffer)
|
725
|
+
self.return_code, _ignore = buffer.unpack('C')
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
729
|
+
end
|
730
|
+
|
731
|
+
|
732
|
+
# An enumeration of the MQTT-SN packet types
|
733
|
+
PACKET_TYPES = {
|
734
|
+
0x00 => MQTT::SN::Packet::Advertise,
|
735
|
+
0x01 => MQTT::SN::Packet::Searchgw,
|
736
|
+
0x02 => MQTT::SN::Packet::Gwinfo,
|
737
|
+
0x04 => MQTT::SN::Packet::Connect,
|
738
|
+
0x05 => MQTT::SN::Packet::Connack,
|
739
|
+
0x06 => MQTT::SN::Packet::Willtopicreq,
|
740
|
+
0x07 => MQTT::SN::Packet::Willtopic,
|
741
|
+
0x08 => MQTT::SN::Packet::Willmsgreq,
|
742
|
+
0x09 => MQTT::SN::Packet::Willmsg,
|
743
|
+
0x0a => MQTT::SN::Packet::Register,
|
744
|
+
0x0b => MQTT::SN::Packet::Regack,
|
745
|
+
0x0c => MQTT::SN::Packet::Publish,
|
746
|
+
0x0d => MQTT::SN::Packet::Puback,
|
747
|
+
0x0e => MQTT::SN::Packet::Pubcomp,
|
748
|
+
0x0f => MQTT::SN::Packet::Pubrec,
|
749
|
+
0x10 => MQTT::SN::Packet::Pubrel,
|
750
|
+
0x12 => MQTT::SN::Packet::Subscribe,
|
751
|
+
0x13 => MQTT::SN::Packet::Suback,
|
752
|
+
0x14 => MQTT::SN::Packet::Unsubscribe,
|
753
|
+
0x15 => MQTT::SN::Packet::Unsuback,
|
754
|
+
0x16 => MQTT::SN::Packet::Pingreq,
|
755
|
+
0x17 => MQTT::SN::Packet::Pingresp,
|
756
|
+
0x18 => MQTT::SN::Packet::Disconnect,
|
757
|
+
0x1a => MQTT::SN::Packet::Willtopicupd,
|
758
|
+
0x1b => MQTT::SN::Packet::Willtopicresp,
|
759
|
+
0x1c => MQTT::SN::Packet::Willmsgupd,
|
760
|
+
0x1d => MQTT::SN::Packet::Willmsgresp,
|
761
|
+
}
|
762
|
+
|
763
|
+
end
|