mqtt-rails 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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