go-mqtt 0.0.1

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.
@@ -0,0 +1,720 @@
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
@@ -0,0 +1,4 @@
1
+ module MQTT
2
+ # The version number of the MQTT gem
3
+ VERSION = '0.0.1'
4
+ end