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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE +210 -0
- data/README.md +323 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/mqtt-rails.rb +144 -0
- data/lib/mqtt_rails/client.rb +414 -0
- data/lib/mqtt_rails/connection_helper.rb +172 -0
- data/lib/mqtt_rails/exception.rb +52 -0
- data/lib/mqtt_rails/handler.rb +274 -0
- data/lib/mqtt_rails/packet.rb +33 -0
- data/lib/mqtt_rails/packet/base.rb +315 -0
- data/lib/mqtt_rails/packet/connack.rb +102 -0
- data/lib/mqtt_rails/packet/connect.rb +183 -0
- data/lib/mqtt_rails/packet/disconnect.rb +38 -0
- data/lib/mqtt_rails/packet/pingreq.rb +29 -0
- data/lib/mqtt_rails/packet/pingresp.rb +38 -0
- data/lib/mqtt_rails/packet/puback.rb +44 -0
- data/lib/mqtt_rails/packet/pubcomp.rb +44 -0
- data/lib/mqtt_rails/packet/publish.rb +148 -0
- data/lib/mqtt_rails/packet/pubrec.rb +44 -0
- data/lib/mqtt_rails/packet/pubrel.rb +62 -0
- data/lib/mqtt_rails/packet/suback.rb +75 -0
- data/lib/mqtt_rails/packet/subscribe.rb +124 -0
- data/lib/mqtt_rails/packet/unsuback.rb +49 -0
- data/lib/mqtt_rails/packet/unsubscribe.rb +84 -0
- data/lib/mqtt_rails/publisher.rb +181 -0
- data/lib/mqtt_rails/sender.rb +129 -0
- data/lib/mqtt_rails/ssl_helper.rb +61 -0
- data/lib/mqtt_rails/subscriber.rb +166 -0
- data/lib/mqtt_rails/version.rb +3 -0
- data/mqtt-rails.gemspec +33 -0
- data/samples/client_blocking(reading).rb +29 -0
- data/samples/client_blocking(writing).rb +18 -0
- data/samples/getting_started.rb +49 -0
- data/samples/test_client.rb +69 -0
- 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
|