yalgaar 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/doc/Encoding.html +250 -0
  3. data/doc/MQTT.html +119 -0
  4. data/doc/MQTT/Client.html +1520 -0
  5. data/doc/MQTT/Exception.html +104 -0
  6. data/doc/MQTT/MQTT.html +95 -0
  7. data/doc/MQTT/MQTT/MQTT.html +95 -0
  8. data/doc/MQTT/MQTT/MQTT/Packet.html +1263 -0
  9. data/doc/MQTT/MQTT/MQTT/Packet/Connack.html +433 -0
  10. data/doc/MQTT/MQTT/MQTT/Packet/Connect.html +596 -0
  11. data/doc/MQTT/MQTT/MQTT/Packet/Disconnect.html +214 -0
  12. data/doc/MQTT/MQTT/MQTT/Packet/Pingreq.html +212 -0
  13. data/doc/MQTT/MQTT/MQTT/Packet/Pingresp.html +212 -0
  14. data/doc/MQTT/MQTT/MQTT/Packet/Puback.html +235 -0
  15. data/doc/MQTT/MQTT/MQTT/Packet/Pubcomp.html +235 -0
  16. data/doc/MQTT/MQTT/MQTT/Packet/Publish.html +590 -0
  17. data/doc/MQTT/MQTT/MQTT/Packet/Pubrec.html +235 -0
  18. data/doc/MQTT/MQTT/MQTT/Packet/Pubrel.html +334 -0
  19. data/doc/MQTT/MQTT/MQTT/Packet/Suback.html +441 -0
  20. data/doc/MQTT/MQTT/MQTT/Packet/Subscribe.html +449 -0
  21. data/doc/MQTT/MQTT/MQTT/Packet/Unsuback.html +284 -0
  22. data/doc/MQTT/MQTT/MQTT/Packet/Unsubscribe.html +403 -0
  23. data/doc/MQTT/NotConnectedException.html +106 -0
  24. data/doc/MQTT/ProtocolException.html +105 -0
  25. data/doc/MQTT/Proxy.html +400 -0
  26. data/doc/MQTT/SN.html +116 -0
  27. data/doc/MQTT/SN/Packet.html +802 -0
  28. data/doc/MQTT/SN/Packet/Advertise.html +236 -0
  29. data/doc/MQTT/SN/Packet/Connack.html +259 -0
  30. data/doc/MQTT/SN/Packet/Connect.html +246 -0
  31. data/doc/MQTT/SN/Packet/Disconnect.html +231 -0
  32. data/doc/MQTT/SN/Packet/Gwinfo.html +241 -0
  33. data/doc/MQTT/SN/Packet/Pingreq.html +102 -0
  34. data/doc/MQTT/SN/Packet/Pingresp.html +102 -0
  35. data/doc/MQTT/SN/Packet/Puback.html +257 -0
  36. data/doc/MQTT/SN/Packet/Pubcomp.html +227 -0
  37. data/doc/MQTT/SN/Packet/Publish.html +255 -0
  38. data/doc/MQTT/SN/Packet/Pubrec.html +227 -0
  39. data/doc/MQTT/SN/Packet/Pubrel.html +227 -0
  40. data/doc/MQTT/SN/Packet/Regack.html +257 -0
  41. data/doc/MQTT/SN/Packet/Register.html +257 -0
  42. data/doc/MQTT/SN/Packet/Searchgw.html +223 -0
  43. data/doc/MQTT/SN/Packet/Suback.html +255 -0
  44. data/doc/MQTT/SN/Packet/Subscribe.html +255 -0
  45. data/doc/MQTT/SN/Packet/Unsuback.html +227 -0
  46. data/doc/MQTT/SN/Packet/Unsubscribe.html +255 -0
  47. data/doc/MQTT/SN/Packet/Willmsg.html +209 -0
  48. data/doc/MQTT/SN/Packet/Willmsgreq.html +102 -0
  49. data/doc/MQTT/SN/Packet/Willmsgresp.html +227 -0
  50. data/doc/MQTT/SN/Packet/Willmsgupd.html +209 -0
  51. data/doc/MQTT/SN/Packet/Willtopic.html +233 -0
  52. data/doc/MQTT/SN/Packet/Willtopicreq.html +102 -0
  53. data/doc/MQTT/SN/Packet/Willtopicresp.html +227 -0
  54. data/doc/MQTT/SN/Packet/Willtopicupd.html +232 -0
  55. data/doc/MQTT/SN/ProtocolException.html +105 -0
  56. data/doc/MYalgaar.html +212 -0
  57. data/doc/Object.html +396 -0
  58. data/doc/SSLcomDVCA_2_crt.html +115 -0
  59. data/doc/String.html +227 -0
  60. data/doc/YALGAAR.html +110 -0
  61. data/doc/Yalgaar.html +189 -0
  62. data/doc/YalgaarApis.html +619 -0
  63. data/doc/YalgaarChannelList.html +228 -0
  64. data/doc/YalgaarConnect.html +439 -0
  65. data/doc/YalgaarHistory.html +174 -0
  66. data/doc/YalgaarInit.html +206 -0
  67. data/doc/YalgaarPublish.html +196 -0
  68. data/doc/YalgaarSubcribe.html +266 -0
  69. data/doc/ca_crt.html +97 -0
  70. data/doc/css/fonts.css +167 -0
  71. data/doc/css/rdoc.css +590 -0
  72. data/doc/examples/SSLcomDVCA_2_crt.html +115 -0
  73. data/doc/fonts/Lato-Light.ttf +0 -0
  74. data/doc/fonts/Lato-LightItalic.ttf +0 -0
  75. data/doc/fonts/Lato-Regular.ttf +0 -0
  76. data/doc/fonts/Lato-RegularItalic.ttf +0 -0
  77. data/doc/fonts/SourceCodePro-Bold.ttf +0 -0
  78. data/doc/fonts/SourceCodePro-Regular.ttf +0 -0
  79. data/doc/images/add.png +0 -0
  80. data/doc/images/arrow_up.png +0 -0
  81. data/doc/images/brick.png +0 -0
  82. data/doc/images/brick_link.png +0 -0
  83. data/doc/images/bug.png +0 -0
  84. data/doc/images/bullet_black.png +0 -0
  85. data/doc/images/bullet_toggle_minus.png +0 -0
  86. data/doc/images/bullet_toggle_plus.png +0 -0
  87. data/doc/images/date.png +0 -0
  88. data/doc/images/delete.png +0 -0
  89. data/doc/images/find.png +0 -0
  90. data/doc/images/macFFBgHack.png +0 -0
  91. data/doc/images/package.png +0 -0
  92. data/doc/images/page_green.png +0 -0
  93. data/doc/images/page_white_text.png +0 -0
  94. data/doc/images/page_white_width.png +0 -0
  95. data/doc/images/plugin.png +0 -0
  96. data/doc/images/ruby.png +0 -0
  97. data/doc/images/tag_blue.png +0 -0
  98. data/doc/images/tag_green.png +0 -0
  99. data/doc/images/transparent.png +0 -0
  100. data/doc/images/wrench.png +0 -0
  101. data/doc/images/wrench_orange.png +0 -0
  102. data/doc/images/zoom.png +0 -0
  103. data/doc/index.html +219 -0
  104. data/doc/js/darkfish.js +161 -0
  105. data/doc/js/jquery.js +4 -0
  106. data/doc/js/navigation.js +142 -0
  107. data/doc/js/navigation.js.gz +0 -0
  108. data/doc/js/search.js +109 -0
  109. data/doc/js/search_index.js +1 -0
  110. data/doc/js/search_index.js.gz +0 -0
  111. data/doc/js/searcher.js +228 -0
  112. data/doc/js/searcher.js.gz +0 -0
  113. data/doc/table_of_contents.html +1296 -0
  114. data/doc/yalgaar_gemspec.html +116 -0
  115. data/examples/SSLcomDVCA_2.crt +34 -0
  116. data/examples/channelList.rb +64 -0
  117. data/examples/connect.rb +37 -0
  118. data/examples/history.rb +37 -0
  119. data/examples/publish.rb +21 -0
  120. data/examples/subcribe.rb +53 -0
  121. data/examples/yalgaarUser.rb +92 -0
  122. data/lib/mqttbridge/YalgaarChannellist.rb +71 -0
  123. data/lib/mqttbridge/YalgaarConnect.rb +277 -0
  124. data/lib/mqttbridge/YalgaarHistory.rb +48 -0
  125. data/lib/mqttbridge/YalgaarInitModule.rb +333 -0
  126. data/lib/mqttbridge/YalgaarPublish.rb +39 -0
  127. data/lib/mqttbridge/YalgaarSubscribe.rb +99 -0
  128. data/lib/mqttbridge/client.rb +601 -0
  129. data/lib/mqttbridge/mqtt.rb +54 -0
  130. data/lib/mqttbridge/packet.rb +1113 -0
  131. data/lib/mqttbridge/patches/string_encoding.rb +34 -0
  132. data/lib/mqttbridge/proxy.rb +119 -0
  133. data/lib/mqttbridge/sn/packet.rb +763 -0
  134. data/lib/mqttbridge/version.rb +4 -0
  135. data/lib/yalgaar.rb +24 -0
  136. metadata +178 -0
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ #~ $LOAD_PATH << '.'
3
+ require 'logger'
4
+ require 'socket'
5
+ require 'thread'
6
+ require 'timeout'
7
+
8
+ require_relative 'version'
9
+ # String encoding monkey patch for Ruby 1.8
10
+ unless String.method_defined?(:force_encoding)
11
+ require 'patches/string_encoding.rb'
12
+ end
13
+
14
+ module MQTT
15
+
16
+ # Default port number for unencrypted connections
17
+ DEFAULT_PORT = 1883
18
+
19
+ # Default port number for TLS/SSL encrypted connections
20
+ DEFAULT_SSL_PORT = 8883
21
+
22
+ # Super-class for other MQTT related exceptions
23
+ class Exception < ::Exception
24
+ end
25
+
26
+ # A ProtocolException will be raised if there is a
27
+ # problem with data received from a remote host
28
+ class ProtocolException < MQTT::Exception
29
+ end
30
+
31
+ # A NotConnectedException will be raised when trying to
32
+ # perform a function but no connection has been
33
+ # established
34
+ class NotConnectedException < MQTT::Exception
35
+ end
36
+
37
+ autoload :Client, 'mqttbridge/client'
38
+ autoload :Packet, 'mqttbridge/packet'
39
+ autoload :Proxy, 'mqttbridge/proxy'
40
+
41
+ # MQTT-SN
42
+ module SN
43
+
44
+ # Default port number for unencrypted connections
45
+ DEFAULT_PORT = 1883
46
+
47
+ # A ProtocolException will be raised if there is a
48
+ # problem with data received from a remote host
49
+ class ProtocolException < MQTT::Exception
50
+ end
51
+
52
+ autoload :Packet, 'sn/packet'
53
+ end
54
+ end
@@ -0,0 +1,1113 @@
1
+ # encoding: BINARY
2
+ require_relative "YalgaarInitModule"
3
+ module MQTT
4
+
5
+ # Class representing a MQTT Packet
6
+ # Performs binary encoding and decoding of headers
7
+ class MQTT::Packet
8
+ include YalgaarApis
9
+ # The version number of the MQTT protocol to use (default 3.1.0)
10
+ attr_accessor :version
11
+
12
+ # Identifier to link related control packets together
13
+ attr_accessor :id
14
+
15
+ # Array of 4 bits in the fixed header
16
+ attr_accessor :flags
17
+
18
+ # The length of the parsed packet body
19
+ attr_reader :body_length
20
+
21
+ # Default attribute values
22
+ ATTR_DEFAULTS = {
23
+ :version => '3.1.0',
24
+ :id => 0,
25
+ :body_length => nil
26
+ }
27
+
28
+ # Read in a packet from a socket
29
+ def self.read(socket)
30
+ # Read in the packet header and create a new packet object
31
+ packet = create_from_header(
32
+ read_byte(socket)
33
+ )
34
+
35
+
36
+ packet.validate_flags
37
+ # Read in the packet length
38
+ multiplier = 1
39
+ body_length = 0
40
+ pos = 1
41
+ begin
42
+ digit = read_byte(socket)
43
+ body_length += ((digit & 0x7F) * multiplier)
44
+ multiplier *= 0x80
45
+ pos += 1
46
+ end while ((digit & 0x80) != 0x00) and pos <= 4
47
+
48
+ # Store the expected body length in the packet
49
+ packet.instance_variable_set('@body_length', body_length)
50
+
51
+ # Read in the packet body
52
+ $subackret=packet.parse_body(socket.read(body_length))
53
+ #~ puts $subackret
54
+ # puts @return_codes
55
+ #~ puts $subackret
56
+ return packet
57
+ end
58
+
59
+ # Parse buffer into new packet object
60
+ def self.parse(buffer)
61
+ packet = parse_header(buffer)
62
+ packet.parse_body(buffer)
63
+ return packet
64
+ end
65
+
66
+ # Parse the header and create a new packet object of the correct type
67
+ # The header is removed from the buffer passed into this function
68
+ def self.parse_header(buffer)
69
+ # Check that the packet is a long as the minimum packet size
70
+ if buffer.bytesize < 2
71
+ raise ProtocolException.new("Invalid packet: less than 2 bytes long")
72
+ end
73
+
74
+ # Create a new packet object
75
+ bytes = buffer.unpack("C5")
76
+ packet = create_from_header(bytes.first)
77
+ packet.validate_flags
78
+
79
+ # Parse the packet length
80
+ body_length = 0
81
+ multiplier = 1
82
+ pos = 1
83
+ begin
84
+ if buffer.bytesize <= pos
85
+ raise ProtocolException.new("The packet length header is incomplete")
86
+ end
87
+ digit = bytes[pos]
88
+ body_length += ((digit & 0x7F) * multiplier)
89
+ multiplier *= 0x80
90
+ pos += 1
91
+ end while ((digit & 0x80) != 0x00) and pos <= 4
92
+
93
+ # Store the expected body length in the packet
94
+ packet.instance_variable_set('@body_length', body_length)
95
+
96
+ # Delete the fixed header from the raw packet passed in
97
+ buffer.slice!(0...pos)
98
+
99
+ return packet
100
+ end
101
+
102
+ # Create a new packet object from the first byte of a MQTT packet
103
+ def self.create_from_header(byte)
104
+ # Work out the class
105
+ type_id = ((byte & 0xF0) >> 4)
106
+ packet_class = MQTT::PACKET_TYPES[type_id]
107
+ if packet_class.nil?
108
+ raise ProtocolException.new("Invalid packet type identifier: #{type_id}")
109
+ end
110
+
111
+ # Convert the last 4 bits of byte into array of true/false
112
+ flags = (0..3).map { |i| byte & (2 ** i) != 0 }
113
+
114
+ # Create a new packet object
115
+ packet_class.new(:flags => flags)
116
+ end
117
+
118
+ # Create a new empty packet
119
+ def initialize(args={})
120
+ # We must set flags before the other values
121
+ @flags = [false, false, false, false]
122
+ update_attributes(ATTR_DEFAULTS.merge(args))
123
+ end
124
+
125
+ # Set packet attributes from a hash of attribute names and values
126
+ def update_attributes(attr={})
127
+ attr.each_pair do |k,v|
128
+ if v.is_a?(Array) or v.is_a?(Hash)
129
+ send("#{k}=", v.dup)
130
+ else
131
+ send("#{k}=", v)
132
+ end
133
+ end
134
+ end
135
+
136
+ # Get the identifer for this packet type
137
+ def type_id
138
+ index = MQTT::PACKET_TYPES.index(self.class)
139
+ if index.nil?
140
+ raise "Invalid packet type: #{self.class}"
141
+ end
142
+ return index
143
+ end
144
+
145
+ # Get the name of the packet type as a string in capitals
146
+ # (like the MQTT specification uses)
147
+ #
148
+ # Example: CONNACK
149
+ def type_name
150
+ self.class.name.split('::').last.upcase
151
+ end
152
+
153
+ # Set the protocol version number
154
+ def version=(arg)
155
+ @version = arg.to_s
156
+ end
157
+
158
+ # Set the length of the packet body
159
+ def body_length=(arg)
160
+ @body_length = arg.to_i
161
+ end
162
+
163
+ # Parse the body (variable header and payload) of a packet
164
+ def parse_body(buffer)
165
+ if buffer.bytesize != body_length
166
+ raise ProtocolException.new(
167
+ "Failed to parse packet - input buffer (#{buffer.bytesize}) is not the same as the body length header (#{body_length})"
168
+ )
169
+ end
170
+ end
171
+
172
+ # Get serialisation of packet's body (variable header and payload)
173
+ def encode_body
174
+ '' # No body by default
175
+ end
176
+
177
+
178
+ # Serialise the packet
179
+ def to_s
180
+ # Encode the fixed header
181
+ header = [
182
+ ((type_id.to_i & 0x0F) << 4) |
183
+ (flags[3] ? 0x8 : 0x0) |
184
+ (flags[2] ? 0x4 : 0x0) |
185
+ (flags[1] ? 0x2 : 0x0) |
186
+ (flags[0] ? 0x1 : 0x0)
187
+ ]
188
+
189
+ # Get the packet's variable header and payload
190
+ body = self.encode_body
191
+
192
+ # Check that that packet isn't too big
193
+ body_length = body.bytesize
194
+ if body_length > 268435455
195
+ raise "Error serialising packet: body is more than 256MB"
196
+ end
197
+
198
+ # Build up the body length field bytes
199
+ begin
200
+ digit = (body_length % 128)
201
+ body_length = body_length.div(128)
202
+ # if there are more digits to encode, set the top bit of this digit
203
+ digit |= 0x80 if (body_length > 0)
204
+ header.push(digit)
205
+ end while (body_length > 0)
206
+
207
+ # Convert header to binary and add on body
208
+ header.pack('C*') + body
209
+ end
210
+
211
+ # Check that fixed header flags are valid for types that don't use the flags
212
+ # @private
213
+ def validate_flags
214
+ if flags != [false, false, false, false]
215
+ raise ProtocolException.new("Invalid flags in #{type_name} packet header")
216
+ end
217
+ end
218
+
219
+ # Returns a human readable string
220
+ def inspect
221
+ "\#<#{self.class}>"
222
+ end
223
+
224
+ protected
225
+
226
+ # Encode an array of bytes and return them
227
+ def encode_bytes(*bytes)
228
+ bytes.pack('C*')
229
+ end
230
+
231
+ # Encode an array of bits and return them
232
+ def encode_bits(bits)
233
+ [bits.map{|b| b ? '1' : '0'}.join].pack('b*')
234
+ end
235
+
236
+ # Encode a 16-bit unsigned integer and return it
237
+ def encode_short(val)
238
+ [val.to_i].pack('n')
239
+ end
240
+
241
+ # Encode a UTF-8 string and return it
242
+ # (preceded by the length of the string)
243
+ def encode_string(str)
244
+ str = str.to_s.encode('UTF-8')
245
+
246
+ # Force to binary, when assembling the packet
247
+ str.force_encoding('ASCII-8BIT')
248
+ encode_short(str.bytesize) + str
249
+ end
250
+
251
+ # Remove a 16-bit unsigned integer from the front of buffer
252
+ def shift_short(buffer)
253
+ bytes = buffer.slice!(0..1)
254
+ bytes.unpack('n').first
255
+ end
256
+
257
+ # Remove one byte from the front of the string
258
+ def shift_byte(buffer)
259
+ buffer.slice!(0...1).unpack('C').first
260
+ end
261
+
262
+ # Remove 8 bits from the front of buffer
263
+ def shift_bits(buffer)
264
+ buffer.slice!(0...1).unpack('b8').first.split('').map {|b| b == '1'}
265
+ end
266
+
267
+ # Remove n bytes from the front of buffer
268
+ def shift_data(buffer,bytes)
269
+ buffer.slice!(0...bytes)
270
+ end
271
+
272
+ # Remove string from the front of buffer
273
+ def shift_string(buffer)
274
+ len = shift_short(buffer)
275
+ str = shift_data(buffer,len)
276
+ # Strings in MQTT v3.1 are all UTF-8
277
+ str.force_encoding('UTF-8')
278
+ end
279
+
280
+
281
+ private
282
+
283
+ # Read and unpack a single byte from a socket
284
+ def self.read_byte(socket)
285
+ byte = socket.read(1)
286
+ #~ if byte.nil?
287
+ #~ raise ProtocolException.new("Failed to read byte from socket")
288
+ #~ end
289
+ byte.unpack('C').first
290
+ end
291
+
292
+
293
+
294
+ ## PACKET SUBCLASSES ##
295
+
296
+
297
+ # Class representing an MQTT Publish message
298
+ class Publish < MQTT::Packet
299
+
300
+ # Duplicate delivery flag
301
+ attr_accessor :duplicate
302
+
303
+ # Retain flag
304
+ attr_accessor :retain
305
+
306
+ # Quality of Service level (0, 1, 2)
307
+ attr_accessor :qos
308
+
309
+ # The topic name to publish to
310
+ attr_accessor :topic
311
+
312
+ # The data to be published
313
+ attr_accessor :payload
314
+
315
+ # Default attribute values
316
+ ATTR_DEFAULTS = {
317
+ :topic => nil,
318
+ :payload => ''
319
+ }
320
+
321
+ # Create a new Publish packet
322
+ def initialize(args={})
323
+ super(ATTR_DEFAULTS.merge(args))
324
+ end
325
+
326
+ def duplicate
327
+ @flags[3]
328
+ end
329
+
330
+ # Set the DUP flag (true/false)
331
+ def duplicate=(arg)
332
+ if arg.kind_of?(Integer)
333
+ @flags[3] = (arg == 0x1)
334
+ else
335
+ @flags[3] = arg
336
+ end
337
+ end
338
+
339
+ def retain
340
+ @flags[0]
341
+ end
342
+
343
+ # Set the retain flag (true/false)
344
+ def retain=(arg)
345
+ if arg.kind_of?(Integer)
346
+ @flags[0] = (arg == 0x1)
347
+ else
348
+ @flags[0] = arg
349
+ end
350
+ end
351
+
352
+ def qos
353
+ (@flags[1] ? 0x01 : 0x00) | (@flags[2] ? 0x02 : 0x00)
354
+ end
355
+
356
+ # Set the Quality of Service level (0/1/2)
357
+ def qos=(arg)
358
+ @qos = arg.to_i
359
+ if @qos < 0 or @qos > 2
360
+ raise "Invalid QoS value: #{@qos}"
361
+ else
362
+ @flags[1] = (arg & 0x01 == 0x01)
363
+ @flags[2] = (arg & 0x02 == 0x02)
364
+ end
365
+ end
366
+
367
+ # Get serialisation of packet's body
368
+ def encode_body
369
+ body = ''
370
+ if @topic.nil? or @topic.to_s.empty?
371
+ raise "Invalid topic name when serialising packet"
372
+ end
373
+ body += encode_string(@topic)
374
+ body += encode_short(@id) unless qos == 0
375
+ body += payload.to_s.dup.force_encoding('ASCII-8BIT')
376
+ return body
377
+ end
378
+
379
+ # Parse the body (variable header and payload) of a Publish packet
380
+ def parse_body(buffer)
381
+ super(buffer)
382
+ @topic = shift_string(buffer)
383
+ @id = shift_short(buffer) unless qos == 0
384
+ @payload = buffer
385
+ end
386
+
387
+ # Check that fixed header flags are valid for this packet type
388
+ # @private
389
+ def validate_flags
390
+ if qos == 3
391
+ raise ProtocolException.new("Invalid packet: QoS value of 3 is not allowed")
392
+ end
393
+ if qos == 0 and duplicate
394
+ raise ProtocolException.new("Invalid packet: DUP cannot be set for QoS 0")
395
+ end
396
+ end
397
+
398
+ # Returns a human readable string, summarising the properties of the packet
399
+ def inspect
400
+ "\#<#{self.class}: " +
401
+ "d#{duplicate ? '1' : '0'}, " +
402
+ "q#{qos}, " +
403
+ "r#{retain ? '1' : '0'}, " +
404
+ "m#{id}, " +
405
+ "'#{topic}', " +
406
+ "#{inspect_payload}>"
407
+ end
408
+
409
+ protected
410
+
411
+ def inspect_payload
412
+ str = payload.to_s
413
+ if str.bytesize < 16 and str =~ /^[ -~]*$/
414
+ "'#{str}'"
415
+ else
416
+ "... (#{str.bytesize} bytes)"
417
+ end
418
+ end
419
+ end
420
+
421
+ # Class representing an MQTT Connect Packet
422
+ class Connect < MQTT::Packet
423
+ # The name of the protocol
424
+ attr_accessor :protocol_name
425
+
426
+ # The version number of the protocol
427
+ attr_accessor :protocol_level
428
+
429
+ # The client identifier string
430
+ attr_accessor :client_id
431
+
432
+ # Set to false to keep a persistent session with the server
433
+ attr_accessor :clean_session
434
+
435
+ # Period the server should keep connection open for between pings
436
+ attr_accessor :keep_alive
437
+
438
+ # The topic name to send the Will message to
439
+ attr_accessor :will_topic
440
+
441
+ # The QoS level to send the Will message as
442
+ attr_accessor :will_qos
443
+
444
+ # Set to true to make the Will message retained
445
+ attr_accessor :will_retain
446
+
447
+ # The payload of the Will message
448
+ attr_accessor :will_payload
449
+
450
+ # The username for authenticating with the server
451
+ attr_accessor :username
452
+
453
+ # The password for authenticating with the server
454
+ attr_accessor :password
455
+
456
+ # Default attribute values
457
+ ATTR_DEFAULTS = {
458
+ :client_id => nil,
459
+ :clean_session => true,
460
+ :keep_alive => 15,
461
+ :will_topic => nil,
462
+ :will_qos => 0,
463
+ :will_retain => false,
464
+ :will_payload => '',
465
+ :username => nil,
466
+ :password => nil,
467
+ }
468
+
469
+ # Create a new Client Connect packet
470
+ def initialize(args={})
471
+ super(ATTR_DEFAULTS.merge(args))
472
+
473
+ if version == '3.1.0' or version == '3.1'
474
+ self.protocol_name ||= 'MQIsdp'
475
+ self.protocol_level ||= 0x03
476
+ elsif version == '3.1.1'
477
+ self.protocol_name ||= 'MQTT'
478
+ self.protocol_level ||= 0x04
479
+ else
480
+ raise ArgumentError.new("Unsupported protocol version: #{version}")
481
+ end
482
+ end
483
+
484
+ # Get serialisation of packet's body
485
+ def encode_body
486
+ body = ''
487
+ if @version == '3.1.0'
488
+ if @client_id.nil? or @client_id.bytesize < 1
489
+ raise "Client identifier too short while serialising packet"
490
+ elsif @client_id.bytesize > 51
491
+ raise "Client identifier too long when serialising packet"
492
+ end
493
+ end
494
+ body += encode_string(@protocol_name)
495
+ body += encode_bytes(@protocol_level.to_i)
496
+
497
+ if @keep_alive < 0
498
+ raise "Invalid keep-alive value: cannot be less than 0"
499
+ end
500
+
501
+ # Set the Connect flags
502
+ @connect_flags = 0
503
+ @connect_flags |= 0x02 if @clean_session
504
+ @connect_flags |= 0x04 unless @will_topic.nil?
505
+ @connect_flags |= ((@will_qos & 0x03) << 3)
506
+ @connect_flags |= 0x20 if @will_retain
507
+ @connect_flags |= 0x40 unless @password.nil?
508
+ @connect_flags |= 0x80 unless @username.nil?
509
+ body += encode_bytes(@connect_flags)
510
+
511
+ body += encode_short(@keep_alive)
512
+ body += encode_string(@client_id)
513
+ unless will_topic.nil?
514
+ body += encode_string(@will_topic)
515
+ # The MQTT v3.1 specification says that the payload is a UTF-8 string
516
+ body += encode_string(@will_payload)
517
+ end
518
+ body += encode_string(@username) unless @username.nil?
519
+ body += encode_string(@password) unless @password.nil?
520
+ return body
521
+ end
522
+
523
+ # Parse the body (variable header and payload) of a Connect packet
524
+ def parse_body(buffer)
525
+ super(buffer)
526
+ @protocol_name = shift_string(buffer)
527
+ @protocol_level = shift_byte(buffer).to_i
528
+ if @protocol_name == 'MQIsdp' and @protocol_level == 3
529
+ @version = '3.1.0'
530
+ elsif @protocol_name == 'MQTT' and @protocol_level == 4
531
+ @version = '3.1.1'
532
+ else
533
+ raise ProtocolException.new(
534
+ "Unsupported protocol: #{@protocol_name}/#{@protocol_level}"
535
+ )
536
+ end
537
+
538
+ @connect_flags = shift_byte(buffer)
539
+ @clean_session = ((@connect_flags & 0x02) >> 1) == 0x01
540
+ @keep_alive = shift_short(buffer)
541
+ @client_id = shift_string(buffer)
542
+ if ((@connect_flags & 0x04) >> 2) == 0x01
543
+ # Last Will and Testament
544
+ @will_qos = ((@connect_flags & 0x18) >> 3)
545
+ @will_retain = ((@connect_flags & 0x20) >> 5) == 0x01
546
+ @will_topic = shift_string(buffer)
547
+ # The MQTT v3.1 specification says that the payload is a UTF-8 string
548
+ @will_payload = shift_string(buffer)
549
+ end
550
+ if ((@connect_flags & 0x80) >> 7) == 0x01 and buffer.bytesize > 0
551
+ @username = shift_string(buffer)
552
+ end
553
+ if ((@connect_flags & 0x40) >> 6) == 0x01 and buffer.bytesize > 0
554
+ @password = shift_string(buffer)
555
+ end
556
+ end
557
+
558
+ # Returns a human readable string, summarising the properties of the packet
559
+ def inspect
560
+ str = "\#<#{self.class}: "
561
+ str += "keep_alive=#{keep_alive}"
562
+ str += ", clean" if clean_session
563
+ str += ", client_id='#{client_id}'"
564
+ str += ", username='#{username}'" unless username.nil?
565
+ str += ", password=..." unless password.nil?
566
+ str += ">"
567
+ end
568
+
569
+ # ---- Deprecated attributes and methods ---- #
570
+ public
571
+
572
+ # @deprecated Please use {#protocol_level} instead
573
+ def protocol_version
574
+ protocol_level
575
+ end
576
+
577
+ # @deprecated Please use {#protocol_level=} instead
578
+ def protocol_version=(args)
579
+ self.protocol_level = args
580
+ end
581
+ end
582
+
583
+ # Class representing an MQTT Connect Acknowledgment Packet
584
+ class Connack < MQTT::Packet
585
+ # Session Present flag
586
+ attr_accessor :session_present
587
+
588
+ # The return code (defaults to 0 for connection accepted)
589
+ attr_accessor :return_code
590
+
591
+ # Default attribute values
592
+ ATTR_DEFAULTS = {:return_code => 0x00}
593
+
594
+ # Create a new Client Connect packet
595
+ def initialize(args={})
596
+ # We must set flags before other attributes
597
+ @connack_flags = [false, false, false, false, false, false, false, false]
598
+ super(ATTR_DEFAULTS.merge(args))
599
+ end
600
+
601
+ # Get the Session Present flag
602
+ def session_present
603
+ @connack_flags[0]
604
+ end
605
+
606
+ # Set the Session Present flag
607
+ def session_present=(arg)
608
+ if arg.kind_of?(Integer)
609
+ @connack_flags[0] = (arg == 0x1)
610
+ else
611
+ @connack_flags[0] = arg
612
+ end
613
+ end
614
+
615
+ # Get a string message corresponding to a return code
616
+ def return_msg
617
+ case return_code
618
+ when 0x00
619
+ "Connection Accepted"
620
+ when 0x01
621
+ "Connection refused: unacceptable protocol version"
622
+ when 0x02
623
+ "Connection refused: client identifier rejected"
624
+ when 0x03
625
+ "Connection refused: server unavailable"
626
+ when 0x04
627
+ "Connection refused: bad user name or password"
628
+ when 0x05
629
+ "Connection refused: not authorised"
630
+ else
631
+ "Connection refused: error code #{return_code}"
632
+ end
633
+ end
634
+
635
+ # Get serialisation of packet's body
636
+ def encode_body
637
+ body = ''
638
+ body += encode_bits(@connack_flags)
639
+ body += encode_bytes(@return_code.to_i)
640
+ return body
641
+ end
642
+
643
+ # Parse the body (variable header and payload) of a Connect Acknowledgment packet
644
+ def parse_body(buffer)
645
+ super(buffer)
646
+ @connack_flags = shift_bits(buffer)
647
+ unless @connack_flags[1,7] == [false, false, false, false, false, false, false]
648
+ raise ProtocolException.new("Invalid flags in Connack variable header")
649
+ end
650
+ @return_code = shift_byte(buffer)
651
+ unless buffer.empty?
652
+ raise ProtocolException.new("Extra bytes at end of Connect Acknowledgment packet")
653
+ end
654
+ pubACK(@return_code)
655
+ end
656
+
657
+ # Returns a human readable string, summarising the properties of the packet
658
+ def inspect
659
+ "\#<#{self.class}: 0x%2.2X>" % return_code
660
+ end
661
+ end
662
+
663
+ # Class representing an MQTT Publish Acknowledgment packet
664
+ class Puback < MQTT::Packet
665
+ # Get serialisation of packet's body
666
+ def encode_body
667
+ encode_short(@id)
668
+ end
669
+
670
+ # Parse the body (variable header and payload) of a packet
671
+ def parse_body(buffer)
672
+ super(buffer)
673
+ @id = shift_short(buffer)
674
+ unless buffer.empty?
675
+ raise ProtocolException.new("Extra bytes at end of Publish Acknowledgment packet")
676
+ end
677
+ end
678
+
679
+ # Returns a human readable string, summarising the properties of the packet
680
+ def inspect
681
+ "\#<#{self.class}: 0x%2.2X>" % id
682
+ end
683
+ end
684
+
685
+ # Class representing an MQTT Publish Received packet
686
+ class Pubrec < MQTT::Packet
687
+ # Get serialisation of packet's body
688
+ def encode_body
689
+ encode_short(@id)
690
+ end
691
+
692
+ # Parse the body (variable header and payload) of a packet
693
+ def parse_body(buffer)
694
+ super(buffer)
695
+ @id = shift_short(buffer)
696
+ unless buffer.empty?
697
+ raise ProtocolException.new("Extra bytes at end of Publish Received packet")
698
+ end
699
+ end
700
+
701
+ # Returns a human readable string, summarising the properties of the packet
702
+ def inspect
703
+ "\#<#{self.class}: 0x%2.2X>" % id
704
+ end
705
+ end
706
+
707
+ # Class representing an MQTT Publish Release packet
708
+ class Pubrel < MQTT::Packet
709
+
710
+ # Default attribute values
711
+ ATTR_DEFAULTS = {
712
+ :flags => [false, true, false, false],
713
+ }
714
+
715
+ # Create a new Pubrel packet
716
+ def initialize(args={})
717
+ super(ATTR_DEFAULTS.merge(args))
718
+ end
719
+
720
+ # Get serialisation of packet's body
721
+ def encode_body
722
+ encode_short(@id)
723
+ end
724
+
725
+ # Parse the body (variable header and payload) of a packet
726
+ def parse_body(buffer)
727
+ super(buffer)
728
+ @id = shift_short(buffer)
729
+ unless buffer.empty?
730
+ raise ProtocolException.new("Extra bytes at end of Publish Release packet")
731
+ end
732
+ end
733
+
734
+ # Check that fixed header flags are valid for this packet type
735
+ # @private
736
+ def validate_flags
737
+ if @flags != [false, true, false, false]
738
+ raise ProtocolException.new("Invalid flags in PUBREL packet header")
739
+ end
740
+ end
741
+
742
+ # Returns a human readable string, summarising the properties of the packet
743
+ def inspect
744
+ "\#<#{self.class}: 0x%2.2X>" % id
745
+ end
746
+ end
747
+
748
+ # Class representing an MQTT Publish Complete packet
749
+ class Pubcomp < MQTT::Packet
750
+ # Get serialisation of packet's body
751
+ def encode_body
752
+ encode_short(@id)
753
+ end
754
+
755
+ # Parse the body (variable header and payload) of a packet
756
+ def parse_body(buffer)
757
+ super(buffer)
758
+ @id = shift_short(buffer)
759
+ unless buffer.empty?
760
+ raise ProtocolException.new("Extra bytes at end of Publish Complete packet")
761
+ end
762
+ end
763
+
764
+ # Returns a human readable string, summarising the properties of the packet
765
+ def inspect
766
+ "\#<#{self.class}: 0x%2.2X>" % id
767
+ end
768
+ end
769
+
770
+ # Class representing an MQTT Client Subscribe packet
771
+ class Subscribe < MQTT::Packet
772
+ # One or more topic filters to subscribe to
773
+ attr_accessor :topics
774
+
775
+ # Default attribute values
776
+ ATTR_DEFAULTS = {
777
+ :topics => [],
778
+ :flags => [false, true, false, false],
779
+ }
780
+
781
+ # Create a new Subscribe packet
782
+ def initialize(args={})
783
+ super(ATTR_DEFAULTS.merge(args))
784
+ end
785
+
786
+ # Set one or more topic filters for the Subscribe packet
787
+ # The topics parameter should be one of the following:
788
+ # * String: subscribe to one topic with QoS 0
789
+ # * Array: subscribe to multiple topics with QoS 0
790
+ # * Hash: subscribe to multiple topics where the key is the topic and the value is the QoS level
791
+ #
792
+ # For example:
793
+ # packet.topics = 'a/b'
794
+ # packet.topics = ['a/b', 'c/d']
795
+ # packet.topics = [['a/b',0], ['c/d',1]]
796
+ # packet.topics = {'a/b' => 0, 'c/d' => 1}
797
+ #
798
+ def topics=(value)
799
+ # Get input into a consistent state
800
+ if value.is_a?(Array)
801
+ input = value.flatten
802
+ else
803
+ input = [value]
804
+ end
805
+
806
+ @topics = []
807
+ while(input.length>0)
808
+ item = input.shift
809
+ if item.is_a?(Hash)
810
+ # Convert hash into an ordered array of arrays
811
+ @topics += item.sort
812
+ elsif item.is_a?(String)
813
+ # Peek at the next item in the array, and remove it if it is an integer
814
+ if input.first.is_a?(Integer)
815
+ qos = input.shift
816
+ @topics << [item,qos]
817
+ else
818
+ @topics << [item,0]
819
+ end
820
+ else
821
+ # Meh?
822
+ raise "Invalid topics input: #{value.inspect}"
823
+ end
824
+ end
825
+ @topics
826
+ end
827
+
828
+ # Get serialisation of packet's body
829
+ def encode_body
830
+ if @topics.empty?
831
+ raise "no topics given when serialising packet"
832
+ end
833
+ body = encode_short(@id)
834
+ topics.each do |item|
835
+ body += encode_string(item[0])
836
+ body += encode_bytes(item[1])
837
+ end
838
+ return body
839
+ end
840
+
841
+ # Parse the body (variable header and payload) of a packet
842
+ def parse_body(buffer)
843
+ super(buffer)
844
+ @id = shift_short(buffer)
845
+ @topics = []
846
+ while(buffer.bytesize>0)
847
+ topic_name = shift_string(buffer)
848
+ topic_qos = shift_byte(buffer)
849
+ @topics << [topic_name,topic_qos]
850
+ end
851
+ end
852
+
853
+ # Check that fixed header flags are valid for this packet type
854
+ # @private
855
+ def validate_flags
856
+ if @flags != [false, true, false, false]
857
+ raise ProtocolException.new("Invalid flags in SUBSCRIBE packet header")
858
+ end
859
+ end
860
+
861
+ # Returns a human readable string, summarising the properties of the packet
862
+ def inspect
863
+ _str = "\#<#{self.class}: 0x%2.2X, %s>" % [
864
+ id,
865
+ topics.map {|t| "'#{t[0]}':#{t[1]}"}.join(', ')
866
+ ]
867
+ end
868
+ end
869
+
870
+ # Class representing an MQTT Subscribe Acknowledgment packet
871
+ class Suback < MQTT::Packet
872
+ # An array of return codes, ordered by the topics that were subscribed to
873
+ attr_accessor :return_codes
874
+
875
+ # Default attribute values
876
+ ATTR_DEFAULTS = {
877
+ :return_codes => [],
878
+ }
879
+
880
+ # Create a new Subscribe Acknowledgment packet
881
+ def initialize(args={})
882
+ super(ATTR_DEFAULTS.merge(args))
883
+ end
884
+
885
+ # Set the granted QoS value for each of the topics that were subscribed to
886
+ # Can either be an integer or an array or integers.
887
+ def return_codes=(value)
888
+ if value.is_a?(Array)
889
+ @return_codes = value
890
+ #~ puts @return_codes
891
+ elsif value.is_a?(Integer)
892
+ @return_codes = [value]
893
+ #~ puts @return_codes
894
+ else
895
+ raise "return_codes should be an integer or an array of return codes"
896
+ end
897
+ end
898
+
899
+ # Get serialisation of packet's body
900
+ def encode_body
901
+ if @return_codes.empty?
902
+ raise "no granted QoS given when serialising packet"
903
+ end
904
+ body = encode_short(@id)
905
+ return_codes.each { |qos| body += encode_bytes(qos) }
906
+ return body
907
+ end
908
+
909
+ # Parse the body (variable header and payload) of a packet
910
+ def parse_body(buffer)
911
+ super(buffer)
912
+ @id = shift_short(buffer)
913
+ while(buffer.bytesize>0)
914
+ @return_codes << shift_byte(buffer)
915
+ end
916
+ @return_codes
917
+ end
918
+
919
+ # Returns a human readable string, summarising the properties of the packet
920
+ def inspect
921
+ "\#<#{self.class}: 0x%2.2X, rc=%s>" % [id, return_codes.map{|rc| "0x%2.2X" % rc}.join(',')]
922
+ end
923
+
924
+ # ---- Deprecated attributes and methods ---- #
925
+ public
926
+
927
+ # @deprecated Please use {#return_codes} instead
928
+ def granted_qos
929
+ #~ puts __LINE__
930
+ return_codes
931
+ end
932
+
933
+ # @deprecated Please use {#return_codes=} instead
934
+ def granted_qos=(args)
935
+ #~ puts __LINE__
936
+ #~ puts args
937
+ self.return_codes = args
938
+ end
939
+ end
940
+
941
+ # Class representing an MQTT Client Unsubscribe packet
942
+ class Unsubscribe < MQTT::Packet
943
+ # One or more topic paths to unsubscribe from
944
+ attr_accessor :topics
945
+
946
+ # Default attribute values
947
+ ATTR_DEFAULTS = {
948
+ :topics => [],
949
+ :flags => [false, true, false, false],
950
+ }
951
+
952
+ # Create a new Unsubscribe packet
953
+ def initialize(args={})
954
+ super(ATTR_DEFAULTS.merge(args))
955
+ end
956
+
957
+ # Set one or more topic paths to unsubscribe from
958
+ def topics=(value)
959
+ if value.is_a?(Array)
960
+ @topics = value
961
+ else
962
+ @topics = [value]
963
+ end
964
+ end
965
+
966
+ # Get serialisation of packet's body
967
+ def encode_body
968
+ if @topics.empty?
969
+ raise "no topics given when serialising packet"
970
+ end
971
+ body = encode_short(@id)
972
+ topics.each { |topic| body += encode_string(topic) }
973
+ return body
974
+ end
975
+
976
+ # Parse the body (variable header and payload) of a packet
977
+ def parse_body(buffer)
978
+ super(buffer)
979
+ @id = shift_short(buffer)
980
+ while(buffer.bytesize>0)
981
+ @topics << shift_string(buffer)
982
+ end
983
+ end
984
+
985
+ # Check that fixed header flags are valid for this packet type
986
+ # @private
987
+ def validate_flags
988
+ if @flags != [false, true, false, false]
989
+ raise ProtocolException.new("Invalid flags in UNSUBSCRIBE packet header")
990
+ end
991
+ end
992
+
993
+ # Returns a human readable string, summarising the properties of the packet
994
+ def inspect
995
+ "\#<#{self.class}: 0x%2.2X, %s>" % [
996
+ id,
997
+ topics.map {|t| "'#{t}'"}.join(', ')
998
+ ]
999
+ end
1000
+ end
1001
+
1002
+ # Class representing an MQTT Unsubscribe Acknowledgment packet
1003
+ class Unsuback < MQTT::Packet
1004
+ # Create a new Unsubscribe Acknowledgment packet
1005
+ def initialize(args={})
1006
+ super(args)
1007
+ end
1008
+
1009
+ # Get serialisation of packet's body
1010
+ def encode_body
1011
+ encode_short(@id)
1012
+ end
1013
+
1014
+ # Parse the body (variable header and payload) of a packet
1015
+ def parse_body(buffer)
1016
+ super(buffer)
1017
+ @id = shift_short(buffer)
1018
+ unless buffer.empty?
1019
+ raise ProtocolException.new("Extra bytes at end of Unsubscribe Acknowledgment packet")
1020
+ end
1021
+ end
1022
+
1023
+ # Returns a human readable string, summarising the properties of the packet
1024
+ def inspect
1025
+ "\#<#{self.class}: 0x%2.2X>" % id
1026
+ end
1027
+ end
1028
+
1029
+ # Class representing an MQTT Ping Request packet
1030
+ class Pingreq < MQTT::Packet
1031
+ # Create a new Ping Request packet
1032
+ def initialize(args={})
1033
+ super(args)
1034
+ end
1035
+
1036
+ # Check the body
1037
+ def parse_body(buffer)
1038
+ super(buffer)
1039
+ unless buffer.empty?
1040
+ raise ProtocolException.new("Extra bytes at end of Ping Request packet")
1041
+ end
1042
+ end
1043
+ end
1044
+
1045
+ # Class representing an MQTT Ping Response packet
1046
+ class Pingresp < MQTT::Packet
1047
+ # Create a new Ping Response packet
1048
+ def initialize(args={})
1049
+ super(args)
1050
+ end
1051
+
1052
+ # Check the body
1053
+ def parse_body(buffer)
1054
+ super(buffer)
1055
+ unless buffer.empty?
1056
+ raise ProtocolException.new("Extra bytes at end of Ping Response packet")
1057
+ end
1058
+ end
1059
+ end
1060
+
1061
+ # Class representing an MQTT Client Disconnect packet
1062
+ class Disconnect < MQTT::Packet
1063
+ # Create a new Client Disconnect packet
1064
+ def initialize(args={})
1065
+ super(args)
1066
+ end
1067
+
1068
+ # Check the body
1069
+ def parse_body(buffer)
1070
+ super(buffer)
1071
+ unless buffer.empty?
1072
+ raise ProtocolException.new("Extra bytes at end of Disconnect packet")
1073
+ end
1074
+ end
1075
+ end
1076
+
1077
+
1078
+ # ---- Deprecated attributes and methods ---- #
1079
+ public
1080
+
1081
+ # @deprecated Please use {#id} instead
1082
+ def message_id
1083
+ id
1084
+ end
1085
+
1086
+ # @deprecated Please use {#id=} instead
1087
+ def message_id=(args)
1088
+ self.id = args
1089
+ end
1090
+ end
1091
+
1092
+
1093
+ # An enumeration of the MQTT packet types
1094
+ PACKET_TYPES = [
1095
+ nil,
1096
+ MQTT::Packet::Connect,
1097
+ MQTT::Packet::Connack,
1098
+ MQTT::Packet::Publish,
1099
+ MQTT::Packet::Puback,
1100
+ MQTT::Packet::Pubrec,
1101
+ MQTT::Packet::Pubrel,
1102
+ MQTT::Packet::Pubcomp,
1103
+ MQTT::Packet::Subscribe,
1104
+ MQTT::Packet::Suback,
1105
+ MQTT::Packet::Unsubscribe,
1106
+ MQTT::Packet::Unsuback,
1107
+ MQTT::Packet::Pingreq,
1108
+ MQTT::Packet::Pingresp,
1109
+ MQTT::Packet::Disconnect,
1110
+ nil
1111
+ ]
1112
+
1113
+ end