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