mqtt-rails 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/CODE_OF_CONDUCT.md +49 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +210 -0
  8. data/README.md +323 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/lib/mqtt-rails.rb +144 -0
  13. data/lib/mqtt_rails/client.rb +414 -0
  14. data/lib/mqtt_rails/connection_helper.rb +172 -0
  15. data/lib/mqtt_rails/exception.rb +52 -0
  16. data/lib/mqtt_rails/handler.rb +274 -0
  17. data/lib/mqtt_rails/packet.rb +33 -0
  18. data/lib/mqtt_rails/packet/base.rb +315 -0
  19. data/lib/mqtt_rails/packet/connack.rb +102 -0
  20. data/lib/mqtt_rails/packet/connect.rb +183 -0
  21. data/lib/mqtt_rails/packet/disconnect.rb +38 -0
  22. data/lib/mqtt_rails/packet/pingreq.rb +29 -0
  23. data/lib/mqtt_rails/packet/pingresp.rb +38 -0
  24. data/lib/mqtt_rails/packet/puback.rb +44 -0
  25. data/lib/mqtt_rails/packet/pubcomp.rb +44 -0
  26. data/lib/mqtt_rails/packet/publish.rb +148 -0
  27. data/lib/mqtt_rails/packet/pubrec.rb +44 -0
  28. data/lib/mqtt_rails/packet/pubrel.rb +62 -0
  29. data/lib/mqtt_rails/packet/suback.rb +75 -0
  30. data/lib/mqtt_rails/packet/subscribe.rb +124 -0
  31. data/lib/mqtt_rails/packet/unsuback.rb +49 -0
  32. data/lib/mqtt_rails/packet/unsubscribe.rb +84 -0
  33. data/lib/mqtt_rails/publisher.rb +181 -0
  34. data/lib/mqtt_rails/sender.rb +129 -0
  35. data/lib/mqtt_rails/ssl_helper.rb +61 -0
  36. data/lib/mqtt_rails/subscriber.rb +166 -0
  37. data/lib/mqtt_rails/version.rb +3 -0
  38. data/mqtt-rails.gemspec +33 -0
  39. data/samples/client_blocking(reading).rb +29 -0
  40. data/samples/client_blocking(writing).rb +18 -0
  41. data/samples/getting_started.rb +49 -0
  42. data/samples/test_client.rb +69 -0
  43. metadata +126 -0
@@ -0,0 +1,33 @@
1
+ # encoding: BINARY
2
+ # Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
3
+ #
4
+ # All rights reserved. This program and the accompanying materials
5
+ # are made available under the terms of the Eclipse Public License v1.0
6
+ # and Eclipse Distribution License v1.0 which accompany this distribution.
7
+ #
8
+ # The Eclipse Public License is available at
9
+ # https://eclipse.org/org/documents/epl-v10.php.
10
+ # and the Eclipse Distribution License is available at
11
+ # https://eclipse.org/org/documents/edl-v10.php.
12
+ #
13
+ # Contributors:
14
+ # Pierre Goudet - initial committer
15
+
16
+ require "mqtt_rails/packet/base"
17
+ require "mqtt_rails/packet/connect"
18
+ require "mqtt_rails/packet/connack"
19
+ require "mqtt_rails/packet/publish"
20
+ require "mqtt_rails/packet/puback"
21
+ require "mqtt_rails/packet/pubrec"
22
+ require "mqtt_rails/packet/pubrel"
23
+ require "mqtt_rails/packet/pubcomp"
24
+ require "mqtt_rails/packet/subscribe"
25
+ require "mqtt_rails/packet/suback"
26
+ require "mqtt_rails/packet/unsubscribe"
27
+ require "mqtt_rails/packet/unsuback"
28
+ require "mqtt_rails/packet/pingreq"
29
+ require "mqtt_rails/packet/pingresp"
30
+ require "mqtt_rails/packet/disconnect"
31
+
32
+ module MqttRails
33
+ end
@@ -0,0 +1,315 @@
1
+ # encoding: BINARY
2
+ ### original file from the ruby-mqtt gem
3
+ ### located at https://github.com/njh/ruby-mqtt/blob/master/lib/mqtt/packet.rb
4
+ ### Copyright (c) 2009-2013 Nicholas J Humfrey
5
+
6
+ # Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
7
+ #
8
+ # All rights reserved. This program and the accompanying materials
9
+ # are made available under the terms of the Eclipse Public License v1.0
10
+ # and Eclipse Distribution License v1.0 which accompany this distribution.
11
+ #
12
+ # The Eclipse Public License is available at
13
+ # https://eclipse.org/org/documents/epl-v10.php.
14
+ # and the Eclipse Distribution License is available at
15
+ # https://eclipse.org/org/documents/edl-v10.php.
16
+ #
17
+ # Contributors:
18
+ # Pierre Goudet - initial committer
19
+
20
+ module MqttRails
21
+ module Packet
22
+ # Class representing a MQTT Packet
23
+ # Performs binary encoding and decoding of headers
24
+ class Base
25
+ # The version number of the MQTT protocol to use (default 3.1.0)
26
+ attr_accessor :version
27
+
28
+ # Identifier to link related control packets together
29
+ attr_accessor :id
30
+
31
+ # Array of 4 bits in the fixed header
32
+ attr_accessor :flags
33
+
34
+ # The length of the parsed packet body
35
+ attr_reader :body_length
36
+
37
+ # Default attribute values
38
+ ATTR_DEFAULTS = {
39
+ :version => '3.1.0',
40
+ :id => 0,
41
+ :body_length => nil
42
+ }
43
+
44
+ # Read in a packet from a socket
45
+ def self.read(socket)
46
+ # Read in the packet header and create a new packet object
47
+ packet = create_from_header(
48
+ read_byte(socket)
49
+ )
50
+
51
+ unless packet.nil?
52
+ packet.validate_flags
53
+
54
+ # Read in the packet length
55
+ multiplier = 1
56
+ body_length = 0
57
+ pos = 1
58
+ begin
59
+ digit = read_byte(socket)
60
+ body_length += ((digit & 0x7F) * multiplier)
61
+ multiplier *= 0x80
62
+ pos += 1
63
+ end while ((digit & 0x80) != 0x00) && pos <= 4
64
+
65
+ # Store the expected body length in the packet
66
+ packet.instance_variable_set('@body_length', body_length)
67
+
68
+ # Read in the packet body
69
+ packet.parse_body(socket.read(body_length))
70
+ end
71
+ packet
72
+ end
73
+
74
+ # Parse buffer into new packet object
75
+ def self.parse(buffer)
76
+ packet = parse_header(buffer)
77
+ packet.parse_body(buffer)
78
+ packet
79
+ end
80
+
81
+ # Parse the header and create a new packet object of the correct type
82
+ # The header is removed from the buffer passed into this function
83
+ def self.parse_header(buffer)
84
+ # Check that the packet is a long as the minimum packet size
85
+ if buffer.bytesize < 2
86
+ raise MqttRails::PacketFormatException.new(
87
+ "Invalid packet: less than 2 bytes long")
88
+ end
89
+
90
+ # Create a new packet object
91
+ bytes = buffer.unpack("C5")
92
+ packet = create_from_header(bytes.first)
93
+ packet.validate_flags
94
+
95
+ # Parse the packet length
96
+ body_length = 0
97
+ multiplier = 1
98
+ pos = 1
99
+ begin
100
+ if buffer.bytesize <= pos
101
+ raise MqttRails::PacketFormatException.new(
102
+ "The packet length header is incomplete")
103
+ end
104
+ digit = bytes[pos]
105
+ body_length += ((digit & 0x7F) * multiplier)
106
+ multiplier *= 0x80
107
+ pos += 1
108
+ end while ((digit & 0x80) != 0x00) && pos <= 4
109
+
110
+ # Store the expected body length in the packet
111
+ packet.instance_variable_set('@body_length', body_length)
112
+
113
+ # Delete the fixed header from the raw packet passed in
114
+ buffer.slice!(0...pos)
115
+
116
+ packet
117
+ end
118
+
119
+ # Create a new packet object from the first byte of a MQTT packet
120
+ def self.create_from_header(byte)
121
+ unless byte.nil?
122
+ # Work out the class
123
+ type_id = ((byte & 0xF0) >> 4)
124
+ packet_class = MqttRails::PACKET_TYPES[type_id]
125
+ if packet_class.nil?
126
+ raise MqttRails::PacketFormatException.new(
127
+ "Invalid packet type identifier: #{type_id}")
128
+ end
129
+
130
+ # Convert the last 4 bits of byte into array of true/false
131
+ flags = (0..3).map { |i| byte & (2 ** i) != 0 }
132
+
133
+ # Create a new packet object
134
+ packet_class.new(:flags => flags)
135
+ end
136
+ end
137
+
138
+ # Create a new empty packet
139
+ def initialize(args={})
140
+ # We must set flags before the other values
141
+ @flags = [false, false, false, false]
142
+ update_attributes(ATTR_DEFAULTS.merge(args))
143
+ end
144
+
145
+ # Set packet attributes from a hash of attribute names and values
146
+ def update_attributes(attr={})
147
+ attr.each_pair do |k,v|
148
+ if v.is_a?(Array) || v.is_a?(Hash)
149
+ send("#{k}=", v.dup)
150
+ else
151
+ send("#{k}=", v)
152
+ end
153
+ end
154
+ end
155
+
156
+ # Get the identifer for this packet type
157
+ def type_id
158
+ index = MqttRails::PACKET_TYPES.index(self.class)
159
+ if index.nil?
160
+ raise MqttRails::PacketFormatException.new(
161
+ "Invalid packet type: #{self.class}")
162
+ end
163
+ index
164
+ end
165
+
166
+ # Get the name of the packet type as a string in capitals
167
+ # (like the MQTT specification uses)
168
+ #
169
+ # Example: CONNACK
170
+ def type_name
171
+ self.class.name.split('::').last.upcase
172
+ end
173
+
174
+ # Set the protocol version number
175
+ def version=(arg)
176
+ @version = arg.to_s
177
+ end
178
+
179
+ # Set the length of the packet body
180
+ def body_length=(arg)
181
+ @body_length = arg.to_i
182
+ end
183
+
184
+ # Parse the body (variable header and payload) of a packet
185
+ def parse_body(buffer)
186
+ if buffer.bytesize != body_length
187
+ raise MqttRails::PacketFormatException.new(
188
+ "Failed to parse packet - input buffer (#{buffer.bytesize}) is not the same as the body length header (#{body_length})")
189
+ end
190
+ end
191
+
192
+ # Get serialisation of packet's body (variable header and payload)
193
+ def encode_body
194
+ '' # No body by default
195
+ end
196
+
197
+ # Serialise the packet
198
+ def to_s
199
+ # Encode the fixed header
200
+ header = [
201
+ ((type_id.to_i & 0x0F) << 4) |
202
+ (flags[3] ? 0x8 : 0x0) |
203
+ (flags[2] ? 0x4 : 0x0) |
204
+ (flags[1] ? 0x2 : 0x0) |
205
+ (flags[0] ? 0x1 : 0x0)
206
+ ]
207
+
208
+ # Get the packet's variable header and payload
209
+ body = self.encode_body
210
+
211
+ # Check that that packet isn't too big
212
+ body_length = body.bytesize
213
+ if body_length > 268435455
214
+ raise MqttRails::PacketFormatException.new(
215
+ "Error serialising packet: body is more than 256MB")
216
+ end
217
+
218
+ # Build up the body length field bytes
219
+ begin
220
+ digit = (body_length % 128)
221
+ body_length = (body_length / 128)
222
+ # if there are more digits to encode, set the top bit of this digit
223
+ digit |= 0x80 if (body_length > 0)
224
+ header.push(digit)
225
+ end while (body_length > 0)
226
+
227
+ # Convert header to binary and add on body
228
+ header.pack('C*') + body
229
+ end
230
+
231
+ # Check that fixed header flags are valid for types that don't use the flags
232
+ # @private
233
+ def validate_flags
234
+ if flags != [false, false, false, false]
235
+ raise MqttRails::PacketFormatException.new(
236
+ "Invalid flags in #{type_name} packet header")
237
+ end
238
+ end
239
+
240
+ # Returns a human readable string
241
+ def inspect
242
+ "\#<#{self.class}>"
243
+ end
244
+
245
+ protected
246
+
247
+ # Encode an array of bytes and return them
248
+ def encode_bytes(*bytes)
249
+ bytes.pack('C*')
250
+ end
251
+
252
+ # Encode an array of bits and return them
253
+ def encode_bits(bits)
254
+ [bits.map { |b| b ? '1' : '0' }.join].pack('b*')
255
+ end
256
+
257
+ # Encode a 16-bit unsigned integer and return it
258
+ def encode_short(val)
259
+ [val.to_i].pack('n')
260
+ end
261
+
262
+ # Encode a UTF-8 string and return it
263
+ # (preceded by the length of the string)
264
+ def encode_string(str)
265
+ str = str.to_s.encode('UTF-8')
266
+
267
+ # Force to binary, when assembling the packet
268
+ str.force_encoding('ASCII-8BIT')
269
+ encode_short(str.bytesize) + str
270
+ end
271
+
272
+ # Remove a 16-bit unsigned integer from the front of buffer
273
+ def shift_short(buffer)
274
+ bytes = buffer.slice!(0..1)
275
+ bytes.unpack('n').first
276
+ end
277
+
278
+ # Remove one byte from the front of the string
279
+ def shift_byte(buffer)
280
+ buffer.slice!(0...1).unpack('C').first
281
+ end
282
+
283
+ # Remove 8 bits from the front of buffer
284
+ def shift_bits(buffer)
285
+ buffer.slice!(0...1).unpack('b8').first.split('').map { |b| b == '1' }
286
+ end
287
+
288
+ # Remove n bytes from the front of buffer
289
+ def shift_data(buffer,bytes)
290
+ buffer.slice!(0...bytes)
291
+ end
292
+
293
+ # Remove string from the front of buffer
294
+ def shift_string(buffer)
295
+ len = shift_short(buffer)
296
+ str = shift_data(buffer,len)
297
+ # Strings in MQTT v3.1 are all UTF-8
298
+ str.force_encoding('UTF-8')
299
+ end
300
+
301
+
302
+ private
303
+
304
+ # Read and unpack a single byte from a socket
305
+ def self.read_byte(socket)
306
+ byte = socket.read(1)
307
+ unless byte.nil?
308
+ byte.unpack('C').first
309
+ else
310
+ nil
311
+ end
312
+ end
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,102 @@
1
+ # encoding: BINARY
2
+ ### original file from the ruby-mqtt gem
3
+ ### located at https://github.com/njh/ruby-mqtt/blob/master/lib/mqtt/packet.rb
4
+ ### Copyright (c) 2009-2013 Nicholas J Humfrey
5
+
6
+ # Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
7
+ #
8
+ # All rights reserved. This program and the accompanying materials
9
+ # are made available under the terms of the Eclipse Public License v1.0
10
+ # and Eclipse Distribution License v1.0 which accompany this distribution.
11
+ #
12
+ # The Eclipse Public License is available at
13
+ # https://eclipse.org/org/documents/epl-v10.php.
14
+ # and the Eclipse Distribution License is available at
15
+ # https://eclipse.org/org/documents/edl-v10.php.
16
+ #
17
+ # Contributors:
18
+ # Pierre Goudet - initial committer
19
+
20
+ module MqttRails
21
+ module Packet
22
+ class Connack < MqttRails::Packet::Base
23
+ # Session Present flag
24
+ attr_accessor :session_present
25
+
26
+ # The return code (defaults to 0 for connection accepted)
27
+ attr_accessor :return_code
28
+
29
+ # Default attribute values
30
+ ATTR_DEFAULTS = { :return_code => 0x00 }
31
+
32
+ # Create a new Client Connect packet
33
+ def initialize(args={})
34
+ # We must set flags before other attributes
35
+ @connack_flags = [false, false, false, false, false, false, false, false]
36
+ super(ATTR_DEFAULTS.merge(args))
37
+ end
38
+
39
+ # Get the Session Present flag
40
+ def session_present
41
+ @connack_flags[0]
42
+ end
43
+
44
+ # Set the Session Present flag
45
+ def session_present=(arg)
46
+ if arg.kind_of?(Integer)
47
+ @connack_flags[0] = (arg == 0x1)
48
+ else
49
+ @connack_flags[0] = arg
50
+ end
51
+ end
52
+
53
+ # Get a string message corresponding to a return code
54
+ def return_msg
55
+ case return_code
56
+ when 0x00
57
+ "Connection accepted"
58
+ when 0x01
59
+ raise LowVersionException
60
+ when 0x02
61
+ "Connection refused: client identifier rejected"
62
+ when 0x03
63
+ "Connection refused: server unavailable"
64
+ when 0x04
65
+ "Connection refused: bad user name or password"
66
+ when 0x05
67
+ "Connection refused: not authorised"
68
+ else
69
+ "Connection refused: error code #{return_code}"
70
+ end
71
+ end
72
+
73
+ # Get serialisation of packet's body
74
+ def encode_body
75
+ body = ''
76
+ body += encode_bits(@connack_flags)
77
+ body += encode_bytes(@return_code.to_i)
78
+ body
79
+ end
80
+
81
+ # Parse the body (variable header and payload) of a Connect Acknowledgment packet
82
+ def parse_body(buffer)
83
+ super(buffer)
84
+ @connack_flags = shift_bits(buffer)
85
+ unless @connack_flags[1, 7] == [false, false, false, false, false, false, false]
86
+ raise PacketFormatException.new(
87
+ "Invalid flags in Connack variable header")
88
+ end
89
+ @return_code = shift_byte(buffer)
90
+ unless buffer.empty?
91
+ raise PacketFormatException.new(
92
+ "Extra bytes at end of Connect Acknowledgment packet")
93
+ end
94
+ end
95
+
96
+ # Returns a human readable string, summarising the properties of the packet
97
+ def inspect
98
+ "\#<#{self.class}: 0x%2.2X>" % return_code
99
+ end
100
+ end
101
+ end
102
+ end