qubitro-mqtt 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ # Monkey patch to add stubbed string encoding functions to Ruby 1.8
2
+
3
+ class String
4
+ def force_encoding(encoding)
5
+ @encoding = encoding
6
+ self
7
+ end
8
+
9
+ def encoding
10
+ @encoding ||= Encoding::ASCII_8BIT
11
+ end
12
+
13
+ def encode(encoding)
14
+ new = dup
15
+ new.force_encoding(encoding)
16
+ end
17
+ end
18
+
19
+ class Encoding
20
+ attr_reader :name
21
+
22
+ def initialize(name)
23
+ @name = name
24
+ end
25
+
26
+ def to_s
27
+ @name
28
+ end
29
+
30
+ UTF_8 = Encoding.new('UTF-8')
31
+ ASCII_8BIT = Encoding.new('ASCII-8BIT')
32
+ end
33
+
@@ -0,0 +1,116 @@
1
+ module MQTT
2
+ # Class for implementing a proxy to filter/mangle MQTT packets.
3
+ class Proxy
4
+ # Address to bind listening socket to
5
+ attr_reader :local_host
6
+
7
+ # Port to bind listening socket to
8
+ attr_reader :local_port
9
+
10
+ # Address of upstream server to send packets upstream to
11
+ attr_reader :server_host
12
+
13
+ # Port of upstream server to send packets upstream to.
14
+ attr_reader :server_port
15
+
16
+ # Time in seconds before disconnecting an idle connection
17
+ attr_reader :select_timeout
18
+
19
+ # Ruby Logger object to send informational messages to
20
+ attr_reader :logger
21
+
22
+ # A filter Proc for packets coming from the client (to the server).
23
+ attr_writer :client_filter
24
+
25
+ # A filter Proc for packets coming from the server (to the client).
26
+ attr_writer :server_filter
27
+
28
+ # Create a new MQTT Proxy instance.
29
+ #
30
+ # Possible argument keys:
31
+ #
32
+ # :local_host Address to bind listening socket to.
33
+ # :local_port Port to bind listening socket to.
34
+ # :server_host Address of upstream server to send packets upstream to.
35
+ # :server_port Port of upstream server to send packets upstream to.
36
+ # :select_timeout Time in seconds before disconnecting a connection.
37
+ # :logger Ruby Logger object to send informational messages to.
38
+ #
39
+ # NOTE: be careful not to connect to yourself!
40
+ def initialize(args = {})
41
+ @local_host = args[:local_host] || '0.0.0.0'
42
+ @local_port = args[:local_port] || MQTT::DEFAULT_PORT
43
+ @server_host = args[:server_host]
44
+ @server_port = args[:server_port] || 18_830
45
+ @select_timeout = args[:select_timeout] || 60
46
+
47
+ # Setup a logger
48
+ @logger = args[:logger]
49
+ if @logger.nil?
50
+ @logger = Logger.new(STDOUT)
51
+ @logger.level = Logger::INFO
52
+ end
53
+
54
+ # Default is not to have any filters
55
+ @client_filter = nil
56
+ @server_filter = nil
57
+
58
+ # Create TCP server socket
59
+ @server = TCPServer.open(@local_host, @local_port)
60
+ @logger.info "MQTT::Proxy listening on #{@local_host}:#{@local_port}"
61
+ end
62
+
63
+ # Start accepting connections and processing packets.
64
+ def run
65
+ loop do
66
+ # Wait for a client to connect and then create a thread for it
67
+ Thread.new(@server.accept) do |client_socket|
68
+ logger.info "Accepted client: #{client_socket.peeraddr.join(':')}"
69
+ server_socket = TCPSocket.new(@server_host, @server_port)
70
+ begin
71
+ process_packets(client_socket, server_socket)
72
+ rescue Exception => exp
73
+ logger.error exp.to_s
74
+ end
75
+ logger.info "Disconnected: #{client_socket.peeraddr.join(':')}"
76
+ server_socket.close
77
+ client_socket.close
78
+ end
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def process_packets(client_socket, server_socket)
85
+ loop do
86
+ # Wait for some data on either socket
87
+ selected = IO.select([client_socket, server_socket], nil, nil, @select_timeout)
88
+
89
+ # Timeout
90
+ raise 'Timeout in select' if selected.nil?
91
+
92
+ # Iterate through each of the sockets with data to read
93
+ if selected[0].include?(client_socket)
94
+ packet = MQTT::Packet.read(client_socket)
95
+ logger.debug "client -> <#{packet.type_name}>"
96
+ packet = @client_filter.call(packet) unless @client_filter.nil?
97
+ unless packet.nil?
98
+ server_socket.write(packet)
99
+ logger.debug "<#{packet.type_name}> -> server"
100
+ end
101
+ elsif selected[0].include?(server_socket)
102
+ packet = MQTT::Packet.read(server_socket)
103
+ logger.debug "server -> <#{packet.type_name}>"
104
+ packet = @server_filter.call(packet) unless @server_filter.nil?
105
+ unless packet.nil?
106
+ client_socket.write(packet)
107
+ logger.debug "<#{packet.type_name}> -> client"
108
+ end
109
+ else
110
+ logger.error 'Problem with select: socket is neither server or client'
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+
@@ -0,0 +1,721 @@
1
+ # encoding: BINARY
2
+
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
27
+
28
+ packet_class = PACKET_TYPES[type_id]
29
+ if packet_class.nil?
30
+ raise ProtocolException, "Invalid packet type identifier: #{type_id}"
31
+ end
32
+
33
+ # Create a new packet object
34
+ packet = packet_class.new
35
+ packet.parse_body(body)
36
+
37
+ packet
38
+ end
39
+
40
+ # Create a new empty packet
41
+ def initialize(args = {})
42
+ update_attributes(self.class::DEFAULTS.merge(args))
43
+ end
44
+
45
+ def update_attributes(attr = {})
46
+ attr.each_pair do |k, v|
47
+ send("#{k}=", v)
48
+ end
49
+ end
50
+
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
58
+
59
+ # Serialise the packet
60
+ def to_s
61
+ # Get the packet's variable header and payload
62
+ body = encode_body
63
+
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
67
+
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
73
+ end
74
+
75
+ def parse_body(buffer); end
76
+
77
+ protected
78
+
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
86
+
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
96
+ end
97
+
98
+ # Get serialisation of packet's body (variable header and payload)
99
+ def encode_body
100
+ '' # No body by default
101
+ end
102
+
103
+ def encode_flags
104
+ flags = 0x00
105
+ flags += 0x80 if duplicate
106
+ case qos
107
+ when -1
108
+ flags += 0x60
109
+ when 1
110
+ flags += 0x20
111
+ when 2
112
+ flags += 0x40
113
+ end
114
+ flags += 0x10 if retain
115
+ flags += 0x08 if request_will
116
+ flags += 0x04 if clean_session
117
+ case topic_id_type
118
+ when :normal
119
+ flags += 0x0
120
+ when :predefined
121
+ flags += 0x1
122
+ when :short
123
+ flags += 0x2
124
+ end
125
+ flags
126
+ end
127
+
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
139
+ end
140
+ end
141
+
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
149
+ end
150
+
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
155
+ when :normal
156
+ topic_name
157
+ when :short
158
+ if topic_name.nil?
159
+ topic_id
160
+ else
161
+ topic_name
162
+ end
163
+ when :predefined
164
+ [topic_id].pack('n')
165
+ end
166
+ end
167
+
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
172
+ when :normal
173
+ self.topic_name = topic
174
+ when :short
175
+ self.topic_name = topic
176
+ self.topic_id = topic
177
+ when :predefined
178
+ self.topic_id = topic.unpack('n').first
179
+ end
180
+ end
181
+
182
+ class Advertise < Packet
183
+ attr_accessor :gateway_id
184
+ attr_accessor :duration
185
+
186
+ DEFAULTS = {
187
+ :gateway_id => 0x00,
188
+ :duration => 0
189
+ }
190
+
191
+ def encode_body
192
+ [gateway_id, duration].pack('Cn')
193
+ end
194
+
195
+ def parse_body(buffer)
196
+ self.gateway_id, self.duration = buffer.unpack('Cn')
197
+ end
198
+ end
199
+
200
+ class Searchgw < Packet
201
+ attr_accessor :radius
202
+ DEFAULTS = {
203
+ :radius => 1
204
+ }
205
+
206
+ def encode_body
207
+ [radius].pack('C')
208
+ end
209
+
210
+ def parse_body(buffer)
211
+ self.radius, _ignore = buffer.unpack('C')
212
+ end
213
+ end
214
+
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
+ }
222
+
223
+ def encode_body
224
+ [gateway_id, gateway_address].pack('Ca*')
225
+ end
226
+
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
234
+ end
235
+ end
236
+
237
+ class Connect < Packet
238
+ attr_accessor :keep_alive
239
+ attr_accessor :client_id
240
+
241
+ DEFAULTS = {
242
+ :request_will => false,
243
+ :clean_session => true,
244
+ :keep_alive => 15
245
+ }
246
+
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
+ end
255
+
256
+ def parse_body(buffer)
257
+ flags, protocol_id, self.keep_alive, self.client_id = buffer.unpack('CCna*')
258
+
259
+ if protocol_id != 0x01
260
+ raise ProtocolException, "Unsupported protocol ID number: #{protocol_id}"
261
+ end
262
+
263
+ parse_flags(flags)
264
+ end
265
+ end
266
+
267
+ class Connack < Packet
268
+ attr_accessor :return_code
269
+
270
+ # Get a string message corresponding to a return code
271
+ def return_msg
272
+ case return_code
273
+ when 0x00
274
+ 'Accepted'
275
+ when 0x01
276
+ 'Rejected: congestion'
277
+ when 0x02
278
+ 'Rejected: invalid topic ID'
279
+ when 0x03
280
+ 'Rejected: not supported'
281
+ else
282
+ "Rejected: error code #{return_code}"
283
+ end
284
+ end
285
+
286
+ def encode_body
287
+ raise 'return_code must be an Integer' unless return_code.is_a?(Integer)
288
+
289
+ [return_code].pack('C')
290
+ end
291
+
292
+ def parse_body(buffer)
293
+ self.return_code = buffer.unpack('C')[0]
294
+ end
295
+ end
296
+
297
+ class Willtopicreq < Packet
298
+ # No attributes
299
+ end
300
+
301
+ class Willtopic < Packet
302
+ attr_accessor :topic_name
303
+
304
+ DEFAULTS = {
305
+ :qos => 0,
306
+ :retain => false,
307
+ :topic_name => nil
308
+ }
309
+
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
317
+
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)
326
+ end
327
+ end
328
+
329
+ class Willmsgreq < Packet
330
+ # No attributes
331
+ end
332
+
333
+ class Willmsg < Packet
334
+ attr_accessor :data
335
+
336
+ def encode_body
337
+ data
338
+ end
339
+
340
+ def parse_body(buffer)
341
+ self.data = buffer
342
+ end
343
+ end
344
+
345
+ class Register < Packet
346
+ attr_accessor :id
347
+ attr_accessor :topic_id
348
+ attr_accessor :topic_name
349
+
350
+ DEFAULTS = {
351
+ :id => 0x00,
352
+ :topic_id_type => :normal
353
+ }
354
+
355
+ def encode_body
356
+ raise 'id must be an Integer' unless id.is_a?(Integer)
357
+
358
+ raise 'topic_id must be an Integer' unless topic_id.is_a?(Integer)
359
+
360
+ [topic_id, id, topic_name].pack('nna*')
361
+ end
362
+
363
+ def parse_body(buffer)
364
+ self.topic_id, self.id, self.topic_name = buffer.unpack('nna*')
365
+ end
366
+ end
367
+
368
+ class Regack < Packet
369
+ attr_accessor :id
370
+ attr_accessor :topic_id
371
+ attr_accessor :return_code
372
+
373
+ DEFAULTS = {
374
+ :id => 0x00,
375
+ :topic_id => 0x00,
376
+ :topic_id_type => :normal
377
+ }
378
+
379
+ def encode_body
380
+ raise 'id must be an Integer' unless id.is_a?(Integer)
381
+
382
+ raise 'topic_id must be an Integer' unless topic_id.is_a?(Integer)
383
+
384
+ [topic_id, id, return_code].pack('nnC')
385
+ end
386
+
387
+ def parse_body(buffer)
388
+ self.topic_id, self.id, self.return_code = buffer.unpack('nnC')
389
+ end
390
+ end
391
+
392
+ class Publish < Packet
393
+ attr_accessor :topic_id
394
+ attr_accessor :id
395
+ attr_accessor :data
396
+
397
+ DEFAULTS = {
398
+ :id => 0x00,
399
+ :duplicate => false,
400
+ :qos => 0,
401
+ :retain => false,
402
+ :topic_id_type => :normal
403
+ }
404
+
405
+ def encode_body
406
+ raise 'id must be an Integer' unless id.is_a?(Integer)
407
+
408
+ [encode_flags, encode_topic_id, id, data].pack('Cnna*')
409
+ end
410
+
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
416
+ end
417
+
418
+ class Puback < Packet
419
+ attr_accessor :topic_id
420
+ attr_accessor :id
421
+ attr_accessor :return_code
422
+
423
+ DEFAULTS = {
424
+ :id => 0x00,
425
+ :topic_id => nil,
426
+ :return_code => 0x00
427
+ }
428
+
429
+ def encode_body
430
+ raise 'id must be an Integer' unless id.is_a?(Integer)
431
+
432
+ raise 'topic_id must be an Integer' unless topic_id.is_a?(Integer)
433
+
434
+ [topic_id, id, return_code].pack('nnC')
435
+ end
436
+
437
+ def parse_body(buffer)
438
+ self.topic_id, self.id, self.return_code = buffer.unpack('nnC')
439
+ end
440
+ end
441
+
442
+ class Pubcomp < Packet
443
+ attr_accessor :id
444
+
445
+ DEFAULTS = {
446
+ :id => 0x00
447
+ }
448
+
449
+ def encode_body
450
+ raise 'id must be an Integer' unless id.is_a?(Integer)
451
+
452
+ [id].pack('n')
453
+ end
454
+
455
+ def parse_body(buffer)
456
+ self.id, _ignore = buffer.unpack('n')
457
+ end
458
+ end
459
+
460
+ class Pubrec < Packet
461
+ attr_accessor :id
462
+
463
+ DEFAULTS = {
464
+ :id => 0x00
465
+ }
466
+
467
+ def encode_body
468
+ raise 'id must be an Integer' unless id.is_a?(Integer)
469
+
470
+ [id].pack('n')
471
+ end
472
+
473
+ def parse_body(buffer)
474
+ self.id, _ignore = buffer.unpack('n')
475
+ end
476
+ end
477
+
478
+ class Pubrel < Packet
479
+ attr_accessor :id
480
+
481
+ DEFAULTS = {
482
+ :id => 0x00
483
+ }
484
+
485
+ def encode_body
486
+ raise 'id must be an Integer' unless id.is_a?(Integer)
487
+
488
+ [id].pack('n')
489
+ end
490
+
491
+ def parse_body(buffer)
492
+ self.id, _ignore = buffer.unpack('n')
493
+ end
494
+ end
495
+
496
+ class Subscribe < Packet
497
+ attr_accessor :id
498
+ attr_accessor :topic_id
499
+ attr_accessor :topic_name
500
+
501
+ DEFAULTS = {
502
+ :id => 0x00,
503
+ :topic_id_type => :normal
504
+ }
505
+
506
+ def encode_body
507
+ raise 'id must be an Integer' unless id.is_a?(Integer)
508
+
509
+ [encode_flags, id, encode_topic].pack('Cna*')
510
+ end
511
+
512
+ def parse_body(buffer)
513
+ flags, self.id, topic = buffer.unpack('Cna*')
514
+ parse_flags(flags)
515
+ parse_topic(topic)
516
+ end
517
+ end
518
+
519
+ class Suback < Packet
520
+ attr_accessor :id
521
+ attr_accessor :topic_id
522
+ attr_accessor :return_code
523
+
524
+ DEFAULTS = {
525
+ :qos => 0,
526
+ :id => 0x00,
527
+ :topic_id => 0x00,
528
+ :topic_id_type => :normal
529
+ }
530
+
531
+ def encode_body
532
+ raise 'id must be an Integer' unless id.is_a?(Integer)
533
+
534
+ [encode_flags, encode_topic_id, id, return_code].pack('CnnC')
535
+ end
536
+
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
542
+ end
543
+
544
+ class Unsubscribe < Packet
545
+ attr_accessor :id
546
+ attr_accessor :topic_id
547
+ attr_accessor :topic_name
548
+
549
+ DEFAULTS = {
550
+ :id => 0x00,
551
+ :topic_id_type => :normal
552
+ }
553
+
554
+ def encode_body
555
+ raise 'id must be an Integer' unless id.is_a?(Integer)
556
+
557
+ [encode_flags, id, encode_topic].pack('Cna*')
558
+ end
559
+
560
+ def parse_body(buffer)
561
+ flags, self.id, topic = buffer.unpack('Cna*')
562
+ parse_flags(flags)
563
+ parse_topic(topic)
564
+ end
565
+ end
566
+
567
+ class Unsuback < Packet
568
+ attr_accessor :id
569
+
570
+ DEFAULTS = {
571
+ :id => 0x00
572
+ }
573
+
574
+ def encode_body
575
+ raise 'id must be an Integer' unless id.is_a?(Integer)
576
+
577
+ [id].pack('n')
578
+ end
579
+
580
+ def parse_body(buffer)
581
+ self.id = buffer.unpack('n').first
582
+ end
583
+ end
584
+
585
+ class Pingreq < Packet
586
+ # No attributes
587
+ end
588
+
589
+ class Pingresp < Packet
590
+ # No attributes
591
+ end
592
+
593
+ class Disconnect < Packet
594
+ attr_accessor :duration
595
+
596
+ DEFAULTS = {
597
+ :duration => nil
598
+ }
599
+
600
+ def encode_body
601
+ if duration.nil? || duration.zero?
602
+ ''
603
+ else
604
+ [duration].pack('n')
605
+ end
606
+ end
607
+
608
+ def parse_body(buffer)
609
+ self.duration = buffer.length == 2 ? buffer.unpack('n').first : nil
610
+ end
611
+ end
612
+
613
+ class Willtopicupd < Packet
614
+ attr_accessor :topic_name
615
+
616
+ DEFAULTS = {
617
+ :qos => 0,
618
+ :retain => false,
619
+ :topic_name => nil
620
+ }
621
+
622
+ def encode_body
623
+ if topic_name.nil? || topic_name.empty?
624
+ ''
625
+ else
626
+ [encode_flags, topic_name].pack('Ca*')
627
+ end
628
+ end
629
+
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
637
+ end
638
+ end
639
+
640
+ class Willtopicresp < Packet
641
+ attr_accessor :return_code
642
+
643
+ DEFAULTS = {
644
+ :return_code => 0x00
645
+ }
646
+
647
+ def encode_body
648
+ raise 'return_code must be an Integer' unless return_code.is_a?(Integer)
649
+
650
+ [return_code].pack('C')
651
+ end
652
+
653
+ def parse_body(buffer)
654
+ self.return_code, _ignore = buffer.unpack('C')
655
+ end
656
+ end
657
+
658
+ class Willmsgupd < Packet
659
+ attr_accessor :data
660
+
661
+ def encode_body
662
+ data
663
+ end
664
+
665
+ def parse_body(buffer)
666
+ self.data = buffer
667
+ end
668
+ end
669
+
670
+ class Willmsgresp < Packet
671
+ attr_accessor :return_code
672
+
673
+ DEFAULTS = {
674
+ :return_code => 0x00
675
+ }
676
+
677
+ def encode_body
678
+ raise 'return_code must be an Integer' unless return_code.is_a?(Integer)
679
+
680
+ [return_code].pack('C')
681
+ end
682
+
683
+ def parse_body(buffer)
684
+ self.return_code, _ignore = buffer.unpack('C')
685
+ end
686
+ end
687
+ end
688
+
689
+ # An enumeration of the MQTT-SN packet types
690
+ PACKET_TYPES = {
691
+ 0x00 => MQTT::SN::Packet::Advertise,
692
+ 0x01 => MQTT::SN::Packet::Searchgw,
693
+ 0x02 => MQTT::SN::Packet::Gwinfo,
694
+ 0x04 => MQTT::SN::Packet::Connect,
695
+ 0x05 => MQTT::SN::Packet::Connack,
696
+ 0x06 => MQTT::SN::Packet::Willtopicreq,
697
+ 0x07 => MQTT::SN::Packet::Willtopic,
698
+ 0x08 => MQTT::SN::Packet::Willmsgreq,
699
+ 0x09 => MQTT::SN::Packet::Willmsg,
700
+ 0x0a => MQTT::SN::Packet::Register,
701
+ 0x0b => MQTT::SN::Packet::Regack,
702
+ 0x0c => MQTT::SN::Packet::Publish,
703
+ 0x0d => MQTT::SN::Packet::Puback,
704
+ 0x0e => MQTT::SN::Packet::Pubcomp,
705
+ 0x0f => MQTT::SN::Packet::Pubrec,
706
+ 0x10 => MQTT::SN::Packet::Pubrel,
707
+ 0x12 => MQTT::SN::Packet::Subscribe,
708
+ 0x13 => MQTT::SN::Packet::Suback,
709
+ 0x14 => MQTT::SN::Packet::Unsubscribe,
710
+ 0x15 => MQTT::SN::Packet::Unsuback,
711
+ 0x16 => MQTT::SN::Packet::Pingreq,
712
+ 0x17 => MQTT::SN::Packet::Pingresp,
713
+ 0x18 => MQTT::SN::Packet::Disconnect,
714
+ 0x1a => MQTT::SN::Packet::Willtopicupd,
715
+ 0x1b => MQTT::SN::Packet::Willtopicresp,
716
+ 0x1c => MQTT::SN::Packet::Willmsgupd,
717
+ 0x1d => MQTT::SN::Packet::Willmsgresp
718
+ }
719
+ end
720
+ end
721
+