paho-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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/paho-mqtt.rb +7 -0
- data/lib/paho.mqtt/packet_manager.rb +1056 -0
- data/lib/paho.mqtt/paho_client.rb +888 -0
- data/lib/paho.mqtt/version.rb +3 -0
- data/paho-mqtt.gemspec +33 -0
- data/samples/test_aws.rb +31 -0
- data/samples/test_init.rb +64 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 777754009990c745ded4b02654a54bc26a2e422d
|
4
|
+
data.tar.gz: 0e92fba78c0e1938db874d2faed6bee9ef52890c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ee45850a33868e480871d6cf7b50bd8277a9a1d1d81e634341a64548d69c77b5145fa49d4ccd1524be6b4358af7ace9414222f3a074d796b9ebe85ee8d97a7a3
|
7
|
+
data.tar.gz: b295155a5598e8f410b61a4591df6024133e2d9125364d27aa47a4aff10fb308d7ac9c4ac1a7cc8161d5afd5c122ee6b964372c692cba3be7820509426d86866
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at p-goudet@ruby-dev.jp. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Pierre Goudet
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# PahoMqtt
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/PahoMqttRuby`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'paho-mqtt'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install paho-mqtt
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/PahoMqttRuby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
36
|
+
|
37
|
+
|
38
|
+
## License
|
39
|
+
|
40
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
41
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "PahoMqttRuby"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/paho-mqtt.rb
ADDED
@@ -0,0 +1,1056 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
3
|
+
module PahoMqtt
|
4
|
+
# Class representing a MQTT Packet
|
5
|
+
# Performs binary encoding and decoding of headers
|
6
|
+
class Packet
|
7
|
+
# The version number of the MQTT protocol to use (default 3.1.0)
|
8
|
+
attr_accessor :version
|
9
|
+
|
10
|
+
# Identifier to link related control packets together
|
11
|
+
attr_accessor :id
|
12
|
+
|
13
|
+
# Array of 4 bits in the fixed header
|
14
|
+
attr_accessor :flags
|
15
|
+
|
16
|
+
# The length of the parsed packet body
|
17
|
+
attr_reader :body_length
|
18
|
+
|
19
|
+
# Default attribute values
|
20
|
+
ATTR_DEFAULTS = {
|
21
|
+
:version => '3.1.0',
|
22
|
+
:id => 0,
|
23
|
+
:body_length => nil
|
24
|
+
}
|
25
|
+
|
26
|
+
# Read in a packet from a socket
|
27
|
+
def self.read(socket)
|
28
|
+
# Read in the packet header and create a new packet object
|
29
|
+
packet = create_from_header(
|
30
|
+
read_byte(socket)
|
31
|
+
)
|
32
|
+
unless packet.nil?
|
33
|
+
packet.validate_flags
|
34
|
+
|
35
|
+
# Read in the packet length
|
36
|
+
multiplier = 1
|
37
|
+
body_length = 0
|
38
|
+
pos = 1
|
39
|
+
begin
|
40
|
+
digit = read_byte(socket)
|
41
|
+
body_length += ((digit & 0x7F) * multiplier)
|
42
|
+
multiplier *= 0x80
|
43
|
+
pos += 1
|
44
|
+
end while ((digit & 0x80) != 0x00) and pos <= 4
|
45
|
+
|
46
|
+
# Store the expected body length in the packet
|
47
|
+
packet.instance_variable_set('@body_length', body_length)
|
48
|
+
|
49
|
+
# Read in the packet body
|
50
|
+
packet.parse_body( socket.read(body_length) )
|
51
|
+
end
|
52
|
+
return packet
|
53
|
+
end
|
54
|
+
|
55
|
+
# Parse buffer into new packet object
|
56
|
+
def self.parse(buffer)
|
57
|
+
packet = parse_header(buffer)
|
58
|
+
packet.parse_body(buffer)
|
59
|
+
return packet
|
60
|
+
end
|
61
|
+
|
62
|
+
# Parse the header and create a new packet object of the correct type
|
63
|
+
# The header is removed from the buffer passed into this function
|
64
|
+
def self.parse_header(buffer)
|
65
|
+
# Check that the packet is a long as the minimum packet size
|
66
|
+
if buffer.bytesize < 2
|
67
|
+
raise "Invalid packet: less than 2 bytes long"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Create a new packet object
|
71
|
+
bytes = buffer.unpack("C5")
|
72
|
+
packet = create_from_header(bytes.first)
|
73
|
+
packet.validate_flags
|
74
|
+
|
75
|
+
# Parse the packet length
|
76
|
+
body_length = 0
|
77
|
+
multiplier = 1
|
78
|
+
pos = 1
|
79
|
+
begin
|
80
|
+
if buffer.bytesize <= pos
|
81
|
+
raise "The packet length header is incomplete"
|
82
|
+
end
|
83
|
+
digit = bytes[pos]
|
84
|
+
body_length += ((digit & 0x7F) * multiplier)
|
85
|
+
multiplier *= 0x80
|
86
|
+
pos += 1
|
87
|
+
end while ((digit & 0x80) != 0x00) and pos <= 4
|
88
|
+
|
89
|
+
# Store the expected body length in the packet
|
90
|
+
packet.instance_variable_set('@body_length', body_length)
|
91
|
+
|
92
|
+
# Delete the fixed header from the raw packet passed in
|
93
|
+
buffer.slice!(0...pos)
|
94
|
+
|
95
|
+
return packet
|
96
|
+
end
|
97
|
+
|
98
|
+
# Create a new packet object from the first byte of a MQTT packet
|
99
|
+
def self.create_from_header(byte)
|
100
|
+
unless byte.nil?
|
101
|
+
# Work out the class
|
102
|
+
type_id = ((byte & 0xF0) >> 4)
|
103
|
+
packet_class = PahoMqtt::PACKET_TYPES[type_id]
|
104
|
+
if packet_class.nil?
|
105
|
+
raise "Invalid packet type identifier: #{type_id}"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Convert the last 4 bits of byte into array of true/false
|
109
|
+
flags = (0..3).map { |i| byte & (2 ** i) != 0 }
|
110
|
+
|
111
|
+
# Create a new packet object
|
112
|
+
packet_class.new(:flags => flags)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Create a new empty packet
|
117
|
+
def initialize(args={})
|
118
|
+
# We must set flags before the other values
|
119
|
+
@flags = [false, false, false, false]
|
120
|
+
update_attributes(ATTR_DEFAULTS.merge(args))
|
121
|
+
end
|
122
|
+
|
123
|
+
# Set packet attributes from a hash of attribute names and values
|
124
|
+
def update_attributes(attr={})
|
125
|
+
attr.each_pair do |k,v|
|
126
|
+
if v.is_a?(Array) or v.is_a?(Hash)
|
127
|
+
send("#{k}=", v.dup)
|
128
|
+
else
|
129
|
+
send("#{k}=", v)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Get the identifer for this packet type
|
135
|
+
def type_id
|
136
|
+
index = PahoMqtt::PACKET_TYPES.index(self.class)
|
137
|
+
if index.nil?
|
138
|
+
raise "Invalid packet type: #{self.class}"
|
139
|
+
end
|
140
|
+
return index
|
141
|
+
end
|
142
|
+
|
143
|
+
# Get the name of the packet type as a string in capitals
|
144
|
+
# (like the MQTT specification uses)
|
145
|
+
#
|
146
|
+
# Example: CONNACK
|
147
|
+
def type_name
|
148
|
+
self.class.name.split('::').last.upcase
|
149
|
+
end
|
150
|
+
|
151
|
+
# Set the protocol version number
|
152
|
+
def version=(arg)
|
153
|
+
@version = arg.to_s
|
154
|
+
end
|
155
|
+
|
156
|
+
# Set the length of the packet body
|
157
|
+
def body_length=(arg)
|
158
|
+
@body_length = arg.to_i
|
159
|
+
end
|
160
|
+
|
161
|
+
# Parse the body (variable header and payload) of a packet
|
162
|
+
def parse_body(buffer)
|
163
|
+
if buffer.bytesize != body_length
|
164
|
+
raise "Failed to parse packet - input buffer (#{buffer.bytesize}) is not the same as the body length header (#{body_length})"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Get serialisation of packet's body (variable header and payload)
|
169
|
+
def encode_body
|
170
|
+
'' # No body by default
|
171
|
+
end
|
172
|
+
|
173
|
+
# Serialise the packet
|
174
|
+
def to_s
|
175
|
+
# Encode the fixed header
|
176
|
+
header = [
|
177
|
+
((type_id.to_i & 0x0F) << 4) |
|
178
|
+
(flags[3] ? 0x8 : 0x0) |
|
179
|
+
(flags[2] ? 0x4 : 0x0) |
|
180
|
+
(flags[1] ? 0x2 : 0x0) |
|
181
|
+
(flags[0] ? 0x1 : 0x0)
|
182
|
+
]
|
183
|
+
|
184
|
+
# Get the packet's variable header and payload
|
185
|
+
body = self.encode_body
|
186
|
+
|
187
|
+
# Check that that packet isn't too big
|
188
|
+
body_length = body.bytesize
|
189
|
+
if body_length > 268435455
|
190
|
+
raise "Error serialising packet: body is more than 256MB"
|
191
|
+
end
|
192
|
+
|
193
|
+
# Build up the body length field bytes
|
194
|
+
begin
|
195
|
+
digit = (body_length % 128)
|
196
|
+
body_length = (body_length / 128)
|
197
|
+
# if there are more digits to encode, set the top bit of this digit
|
198
|
+
digit |= 0x80 if (body_length > 0)
|
199
|
+
header.push(digit)
|
200
|
+
end while (body_length > 0)
|
201
|
+
|
202
|
+
# Convert header to binary and add on body
|
203
|
+
header.pack('C*') + body
|
204
|
+
end
|
205
|
+
|
206
|
+
# Check that fixed header flags are valid for types that don't use the flags
|
207
|
+
# @private
|
208
|
+
def validate_flags
|
209
|
+
if flags != [false, false, false, false]
|
210
|
+
raise "Invalid flags in #{type_name} packet header"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Returns a human readable string
|
215
|
+
def inspect
|
216
|
+
"\#<#{self.class}>"
|
217
|
+
end
|
218
|
+
|
219
|
+
protected
|
220
|
+
|
221
|
+
# Encode an array of bytes and return them
|
222
|
+
def encode_bytes(*bytes)
|
223
|
+
bytes.pack('C*')
|
224
|
+
end
|
225
|
+
|
226
|
+
# Encode an array of bits and return them
|
227
|
+
def encode_bits(bits)
|
228
|
+
[bits.map{|b| b ? '1' : '0'}.join].pack('b*')
|
229
|
+
end
|
230
|
+
|
231
|
+
# Encode a 16-bit unsigned integer and return it
|
232
|
+
def encode_short(val)
|
233
|
+
[val.to_i].pack('n')
|
234
|
+
end
|
235
|
+
|
236
|
+
# Encode a UTF-8 string and return it
|
237
|
+
# (preceded by the length of the string)
|
238
|
+
def encode_string(str)
|
239
|
+
str = str.to_s.encode('UTF-8')
|
240
|
+
|
241
|
+
# Force to binary, when assembling the packet
|
242
|
+
str.force_encoding('ASCII-8BIT')
|
243
|
+
encode_short(str.bytesize) + str
|
244
|
+
end
|
245
|
+
|
246
|
+
# Remove a 16-bit unsigned integer from the front of buffer
|
247
|
+
def shift_short(buffer)
|
248
|
+
bytes = buffer.slice!(0..1)
|
249
|
+
bytes.unpack('n').first
|
250
|
+
end
|
251
|
+
|
252
|
+
# Remove one byte from the front of the string
|
253
|
+
def shift_byte(buffer)
|
254
|
+
buffer.slice!(0...1).unpack('C').first
|
255
|
+
end
|
256
|
+
|
257
|
+
# Remove 8 bits from the front of buffer
|
258
|
+
def shift_bits(buffer)
|
259
|
+
buffer.slice!(0...1).unpack('b8').first.split('').map {|b| b == '1'}
|
260
|
+
end
|
261
|
+
|
262
|
+
# Remove n bytes from the front of buffer
|
263
|
+
def shift_data(buffer,bytes)
|
264
|
+
buffer.slice!(0...bytes)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Remove string from the front of buffer
|
268
|
+
def shift_string(buffer)
|
269
|
+
len = shift_short(buffer)
|
270
|
+
str = shift_data(buffer,len)
|
271
|
+
# Strings in MQTT v3.1 are all UTF-8
|
272
|
+
str.force_encoding('UTF-8')
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
# Read and unpack a single byte from a socket
|
279
|
+
def self.read_byte(socket)
|
280
|
+
byte = socket.read(1)
|
281
|
+
unless byte.nil?
|
282
|
+
byte.unpack('C').first
|
283
|
+
else
|
284
|
+
nil
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
## PACKET SUBCLASSES ##
|
289
|
+
|
290
|
+
|
291
|
+
# Class representing an MQTT Publish message
|
292
|
+
class Publish < PahoMqtt::Packet
|
293
|
+
|
294
|
+
# Duplicate delivery flag
|
295
|
+
attr_accessor :duplicate
|
296
|
+
|
297
|
+
# Retain flag
|
298
|
+
attr_accessor :retain
|
299
|
+
|
300
|
+
# Quality of Service level (0, 1, 2)
|
301
|
+
attr_accessor :qos
|
302
|
+
|
303
|
+
# The topic name to publish to
|
304
|
+
attr_accessor :topic
|
305
|
+
|
306
|
+
# The data to be published
|
307
|
+
attr_accessor :payload
|
308
|
+
|
309
|
+
# Default attribute values
|
310
|
+
ATTR_DEFAULTS = {
|
311
|
+
:topic => nil,
|
312
|
+
:payload => ''
|
313
|
+
}
|
314
|
+
|
315
|
+
# Create a new Publish packet
|
316
|
+
def initialize(args={})
|
317
|
+
super(ATTR_DEFAULTS.merge(args))
|
318
|
+
end
|
319
|
+
|
320
|
+
def duplicate
|
321
|
+
@flags[3]
|
322
|
+
end
|
323
|
+
|
324
|
+
# Set the DUP flag (true/false)
|
325
|
+
def duplicate=(arg)
|
326
|
+
if arg.kind_of?(Integer)
|
327
|
+
@flags[3] = (arg == 0x1)
|
328
|
+
else
|
329
|
+
@flags[3] = arg
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def retain
|
334
|
+
@flags[0]
|
335
|
+
end
|
336
|
+
|
337
|
+
# Set the retain flag (true/false)
|
338
|
+
def retain=(arg)
|
339
|
+
if arg.kind_of?(Integer)
|
340
|
+
@flags[0] = (arg == 0x1)
|
341
|
+
else
|
342
|
+
@flags[0] = arg
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def qos
|
347
|
+
(@flags[1] ? 0x01 : 0x00) | (@flags[2] ? 0x02 : 0x00)
|
348
|
+
end
|
349
|
+
|
350
|
+
# Set the Quality of Service level (0/1/2)
|
351
|
+
def qos=(arg)
|
352
|
+
@qos = arg.to_i
|
353
|
+
if @qos < 0 or @qos > 2
|
354
|
+
raise "Invalid QoS value: #{@qos}"
|
355
|
+
else
|
356
|
+
@flags[1] = (arg & 0x01 == 0x01)
|
357
|
+
@flags[2] = (arg & 0x02 == 0x02)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
# Get serialisation of packet's body
|
362
|
+
def encode_body
|
363
|
+
body = ''
|
364
|
+
if @topic.nil? or @topic.to_s.empty?
|
365
|
+
raise "Invalid topic name when serialising packet"
|
366
|
+
end
|
367
|
+
body += encode_string(@topic)
|
368
|
+
body += encode_short(@id) unless qos == 0
|
369
|
+
body += payload.to_s.dup.force_encoding('ASCII-8BIT')
|
370
|
+
return body
|
371
|
+
end
|
372
|
+
|
373
|
+
# Parse the body (variable header and payload) of a Publish packet
|
374
|
+
def parse_body(buffer)
|
375
|
+
super(buffer)
|
376
|
+
@topic = shift_string(buffer)
|
377
|
+
@id = shift_short(buffer) unless qos == 0
|
378
|
+
@payload = buffer
|
379
|
+
end
|
380
|
+
|
381
|
+
# Check that fixed header flags are valid for this packet type
|
382
|
+
# @private
|
383
|
+
def validate_flags
|
384
|
+
if qos == 3
|
385
|
+
raise "Invalid packet: QoS value of 3 is not allowed"
|
386
|
+
end
|
387
|
+
if qos == 0 and duplicate
|
388
|
+
raise "Invalid packet: DUP cannot be set for QoS 0"
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# Returns a human readable string, summarising the properties of the packet
|
393
|
+
def inspect
|
394
|
+
"\#<#{self.class}: " +
|
395
|
+
"d#{duplicate ? '1' : '0'}, " +
|
396
|
+
"q#{qos}, " +
|
397
|
+
"r#{retain ? '1' : '0'}, " +
|
398
|
+
"m#{id}, " +
|
399
|
+
"'#{topic}', " +
|
400
|
+
"#{inspect_payload}>"
|
401
|
+
end
|
402
|
+
|
403
|
+
protected
|
404
|
+
|
405
|
+
def inspect_payload
|
406
|
+
str = payload.to_s
|
407
|
+
if str.bytesize < 16 and str =~ /^[ -~]*$/
|
408
|
+
"'#{str}'"
|
409
|
+
else
|
410
|
+
"... (#{str.bytesize} bytes)"
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
# Class representing an MQTT Connect Packet
|
416
|
+
class Connect < PahoMqtt::Packet
|
417
|
+
# The name of the protocol
|
418
|
+
attr_accessor :protocol_name
|
419
|
+
|
420
|
+
# The version number of the protocol
|
421
|
+
attr_accessor :protocol_level
|
422
|
+
|
423
|
+
# The client identifier string
|
424
|
+
attr_accessor :client_id
|
425
|
+
|
426
|
+
# Set to false to keep a persistent session with the server
|
427
|
+
attr_accessor :clean_session
|
428
|
+
|
429
|
+
# Period the server should keep connection open for between pings
|
430
|
+
attr_accessor :keep_alive
|
431
|
+
|
432
|
+
# The topic name to send the Will message to
|
433
|
+
attr_accessor :will_topic
|
434
|
+
|
435
|
+
# The QoS level to send the Will message as
|
436
|
+
attr_accessor :will_qos
|
437
|
+
|
438
|
+
# Set to true to make the Will message retained
|
439
|
+
attr_accessor :will_retain
|
440
|
+
|
441
|
+
# The payload of the Will message
|
442
|
+
attr_accessor :will_payload
|
443
|
+
|
444
|
+
# The username for authenticating with the server
|
445
|
+
attr_accessor :username
|
446
|
+
|
447
|
+
# The password for authenticating with the server
|
448
|
+
attr_accessor :password
|
449
|
+
|
450
|
+
# Default attribute values
|
451
|
+
ATTR_DEFAULTS = {
|
452
|
+
:client_id => nil,
|
453
|
+
:clean_session => true,
|
454
|
+
:keep_alive => 15,
|
455
|
+
:will_topic => nil,
|
456
|
+
:will_qos => 0,
|
457
|
+
:will_retain => false,
|
458
|
+
:will_payload => '',
|
459
|
+
:username => nil,
|
460
|
+
:password => nil,
|
461
|
+
}
|
462
|
+
|
463
|
+
# Create a new Client Connect packet
|
464
|
+
def initialize(args={})
|
465
|
+
super(ATTR_DEFAULTS.merge(args))
|
466
|
+
|
467
|
+
if version == '3.1.0' or version == '3.1'
|
468
|
+
self.protocol_name ||= 'MQIsdp'
|
469
|
+
self.protocol_level ||= 0x03
|
470
|
+
elsif version == '3.1.1'
|
471
|
+
self.protocol_name ||= 'MQTT'
|
472
|
+
self.protocol_level ||= 0x04
|
473
|
+
else
|
474
|
+
raise ArgumentError.new("Unsupported protocol version: #{version}")
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
# Get serialisation of packet's body
|
479
|
+
def encode_body
|
480
|
+
body = ''
|
481
|
+
if @version == '3.1.0'
|
482
|
+
if @client_id.nil? or @client_id.bytesize < 1
|
483
|
+
raise "Client identifier too short while serialising packet"
|
484
|
+
elsif @client_id.bytesize > 23
|
485
|
+
raise "Client identifier too long when serialising packet"
|
486
|
+
end
|
487
|
+
end
|
488
|
+
body += encode_string(@protocol_name)
|
489
|
+
body += encode_bytes(@protocol_level.to_i)
|
490
|
+
if @keep_alive < 0
|
491
|
+
raise "Invalid keep-alive value: cannot be less than 0"
|
492
|
+
end
|
493
|
+
|
494
|
+
# Set the Connect flags
|
495
|
+
@connect_flags = 0
|
496
|
+
@connect_flags |= 0x02 if @clean_session
|
497
|
+
@connect_flags |= 0x04 unless @will_topic.nil?
|
498
|
+
@connect_flags |= ((@will_qos & 0x03) << 3)
|
499
|
+
@connect_flags |= 0x20 if @will_retain
|
500
|
+
@connect_flags |= 0x40 unless @password.nil?
|
501
|
+
@connect_flags |= 0x80 unless @username.nil?
|
502
|
+
body += encode_bytes(@connect_flags)
|
503
|
+
body += encode_short(@keep_alive)
|
504
|
+
body += encode_string(@client_id)
|
505
|
+
unless will_topic.nil?
|
506
|
+
body += encode_string(@will_topic)
|
507
|
+
# The MQTT v3.1 specification says that the payload is a UTF-8 string
|
508
|
+
body += encode_string(@will_payload)
|
509
|
+
end
|
510
|
+
body += encode_string(@username) unless @username.nil?
|
511
|
+
body += encode_string(@password) unless @password.nil?
|
512
|
+
return body
|
513
|
+
end
|
514
|
+
|
515
|
+
# Parse the body (variable header and payload) of a Connect packet
|
516
|
+
def parse_body(buffer)
|
517
|
+
super(buffer)
|
518
|
+
@protocol_name = shift_string(buffer)
|
519
|
+
@protocol_level = shift_byte(buffer).to_i
|
520
|
+
if @protocol_name == 'MQIsdp' and @protocol_level == 3
|
521
|
+
@version = '3.1.0'
|
522
|
+
elsif @protocol_name == 'MQTT' and @protocol_level == 4
|
523
|
+
@version = '3.1.1'
|
524
|
+
else
|
525
|
+
raise "Unsupported protocol: #{@protocol_name}/#{@protocol_level}"
|
526
|
+
end
|
527
|
+
|
528
|
+
@connect_flags = shift_byte(buffer)
|
529
|
+
@clean_session = ((@connect_flags & 0x02) >> 1) == 0x01
|
530
|
+
@keep_alive = shift_short(buffer)
|
531
|
+
@client_id = shift_string(buffer)
|
532
|
+
if ((@connect_flags & 0x04) >> 2) == 0x01
|
533
|
+
# Last Will and Testament
|
534
|
+
@will_qos = ((@connect_flags & 0x18) >> 3)
|
535
|
+
@will_retain = ((@connect_flags & 0x20) >> 5) == 0x01
|
536
|
+
@will_topic = shift_string(buffer)
|
537
|
+
# The MQTT v3.1 specification says that the payload is a UTF-8 string
|
538
|
+
@will_payload = shift_string(buffer)
|
539
|
+
end
|
540
|
+
if ((@connect_flags & 0x80) >> 7) == 0x01 and buffer.bytesize > 0
|
541
|
+
@username = shift_string(buffer)
|
542
|
+
end
|
543
|
+
if ((@connect_flags & 0x40) >> 6) == 0x01 and buffer.bytesize > 0
|
544
|
+
@password = shift_string(buffer)
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
# Returns a human readable string, summarising the properties of the packet
|
549
|
+
def inspect
|
550
|
+
str = "\#<#{self.class}: "
|
551
|
+
str += "keep_alive=#{keep_alive}"
|
552
|
+
str += ", clean" if clean_session
|
553
|
+
str += ", client_id='#{client_id}'"
|
554
|
+
str += ", username='#{username}'" unless username.nil?
|
555
|
+
str += ", password=..." unless password.nil?
|
556
|
+
str += ">"
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
# Class representing an MQTT Connect Acknowledgment Packet
|
561
|
+
class Connack < PahoMqtt::Packet
|
562
|
+
# Session Present flag
|
563
|
+
attr_accessor :session_present
|
564
|
+
|
565
|
+
# The return code (defaults to 0 for connection accepted)
|
566
|
+
attr_accessor :return_code
|
567
|
+
|
568
|
+
# Default attribute values
|
569
|
+
ATTR_DEFAULTS = {:return_code => 0x00}
|
570
|
+
|
571
|
+
# Create a new Client Connect packet
|
572
|
+
def initialize(args={})
|
573
|
+
# We must set flags before other attributes
|
574
|
+
@connack_flags = [false, false, false, false, false, false, false, false]
|
575
|
+
super(ATTR_DEFAULTS.merge(args))
|
576
|
+
end
|
577
|
+
|
578
|
+
# Get the Session Present flag
|
579
|
+
def session_present
|
580
|
+
@connack_flags[0]
|
581
|
+
end
|
582
|
+
|
583
|
+
# Set the Session Present flag
|
584
|
+
def session_present=(arg)
|
585
|
+
if arg.kind_of?(Integer)
|
586
|
+
@connack_flags[0] = (arg == 0x1)
|
587
|
+
else
|
588
|
+
@connack_flags[0] = arg
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
# Get a string message corresponding to a return code
|
593
|
+
def return_msg
|
594
|
+
case return_code
|
595
|
+
when 0x00
|
596
|
+
"Connection Accepted"
|
597
|
+
when 0x01
|
598
|
+
"Connection refused: unacceptable protocol version"
|
599
|
+
when 0x02
|
600
|
+
"Connection refused: client identifier rejected"
|
601
|
+
when 0x03
|
602
|
+
"Connection refused: server unavailable"
|
603
|
+
when 0x04
|
604
|
+
"Connection refused: bad user name or password"
|
605
|
+
when 0x05
|
606
|
+
"Connection refused: not authorised"
|
607
|
+
else
|
608
|
+
"Connection refused: error code #{return_code}"
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
612
|
+
# Get serialisation of packet's body
|
613
|
+
def encode_body
|
614
|
+
body = ''
|
615
|
+
body += encode_bits(@connack_flags)
|
616
|
+
body += encode_bytes(@return_code.to_i)
|
617
|
+
return body
|
618
|
+
end
|
619
|
+
|
620
|
+
# Parse the body (variable header and payload) of a Connect Acknowledgment packet
|
621
|
+
def parse_body(buffer)
|
622
|
+
super(buffer)
|
623
|
+
@connack_flags = shift_bits(buffer)
|
624
|
+
unless @connack_flags[1,7] == [false, false, false, false, false, false, false]
|
625
|
+
raise "Invalid flags in Connack variable header"
|
626
|
+
end
|
627
|
+
@return_code = shift_byte(buffer)
|
628
|
+
unless buffer.empty?
|
629
|
+
raise "Extra bytes at end of Connect Acknowledgment packet"
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
# Returns a human readable string, summarising the properties of the packet
|
634
|
+
def inspect
|
635
|
+
"\#<#{self.class}: 0x%2.2X>" % return_code
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
# Class representing an MQTT Publish Acknowledgment packet
|
640
|
+
class Puback < PahoMqtt::Packet
|
641
|
+
# Get serialisation of packet's body
|
642
|
+
def encode_body
|
643
|
+
encode_short(@id)
|
644
|
+
end
|
645
|
+
|
646
|
+
# Parse the body (variable header and payload) of a packet
|
647
|
+
def parse_body(buffer)
|
648
|
+
super(buffer)
|
649
|
+
@id = shift_short(buffer)
|
650
|
+
unless buffer.empty?
|
651
|
+
raise "Extra bytes at end of Publish Acknowledgment packet"
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
# Returns a human readable string, summarising the properties of the packet
|
656
|
+
def inspect
|
657
|
+
"\#<#{self.class}: 0x%2.2X>" % id
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
# Class representing an MQTT Publish Received packet
|
662
|
+
class Pubrec < PahoMqtt::Packet
|
663
|
+
# Get serialisation of packet's body
|
664
|
+
def encode_body
|
665
|
+
encode_short(@id)
|
666
|
+
end
|
667
|
+
|
668
|
+
# Parse the body (variable header and payload) of a packet
|
669
|
+
def parse_body(buffer)
|
670
|
+
super(buffer)
|
671
|
+
@id = shift_short(buffer)
|
672
|
+
unless buffer.empty?
|
673
|
+
raise "Extra bytes at end of Publish Received packet"
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
# Returns a human readable string, summarising the properties of the packet
|
678
|
+
def inspect
|
679
|
+
"\#<#{self.class}: 0x%2.2X>" % id
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
683
|
+
# Class representing an MQTT Publish Release packet
|
684
|
+
class Pubrel < PahoMqtt::Packet
|
685
|
+
|
686
|
+
# Default attribute values
|
687
|
+
ATTR_DEFAULTS = {
|
688
|
+
:flags => [false, true, false, false],
|
689
|
+
}
|
690
|
+
|
691
|
+
# Create a new Pubrel packet
|
692
|
+
def initialize(args={})
|
693
|
+
super(ATTR_DEFAULTS.merge(args))
|
694
|
+
end
|
695
|
+
|
696
|
+
# Get serialisation of packet's body
|
697
|
+
def encode_body
|
698
|
+
encode_short(@id)
|
699
|
+
end
|
700
|
+
|
701
|
+
# Parse the body (variable header and payload) of a packet
|
702
|
+
def parse_body(buffer)
|
703
|
+
super(buffer)
|
704
|
+
@id = shift_short(buffer)
|
705
|
+
unless buffer.empty?
|
706
|
+
raise "Extra bytes at end of Publish Release packet"
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
710
|
+
# Check that fixed header flags are valid for this packet type
|
711
|
+
# @private
|
712
|
+
def validate_flags
|
713
|
+
if @flags != [false, true, false, false]
|
714
|
+
raise "Invalid flags in PUBREL packet header"
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
718
|
+
# Returns a human readable string, summarising the properties of the packet
|
719
|
+
def inspect
|
720
|
+
"\#<#{self.class}: 0x%2.2X>" % id
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
724
|
+
# Class representing an MQTT Publish Complete packet
|
725
|
+
class Pubcomp < PahoMqtt::Packet
|
726
|
+
# Get serialisation of packet's body
|
727
|
+
def encode_body
|
728
|
+
encode_short(@id)
|
729
|
+
end
|
730
|
+
|
731
|
+
# Parse the body (variable header and payload) of a packet
|
732
|
+
def parse_body(buffer)
|
733
|
+
super(buffer)
|
734
|
+
@id = shift_short(buffer)
|
735
|
+
unless buffer.empty?
|
736
|
+
raise "Extra bytes at end of Publish Complete packet"
|
737
|
+
end
|
738
|
+
end
|
739
|
+
|
740
|
+
# Returns a human readable string, summarising the properties of the packet
|
741
|
+
def inspect
|
742
|
+
"\#<#{self.class}: 0x%2.2X>" % id
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
746
|
+
# Class representing an MQTT Client Subscribe packet
|
747
|
+
class Subscribe < PahoMqtt::Packet
|
748
|
+
# One or more topic filters to subscribe to
|
749
|
+
attr_accessor :topics
|
750
|
+
|
751
|
+
# Default attribute values
|
752
|
+
ATTR_DEFAULTS = {
|
753
|
+
:topics => [],
|
754
|
+
:flags => [false, true, false, false],
|
755
|
+
}
|
756
|
+
|
757
|
+
# Create a new Subscribe packet
|
758
|
+
def initialize(args={})
|
759
|
+
super(ATTR_DEFAULTS.merge(args))
|
760
|
+
end
|
761
|
+
|
762
|
+
# Set one or more topic filters for the Subscribe packet
|
763
|
+
# The topics parameter should be one of the following:
|
764
|
+
# * String: subscribe to one topic with QoS 0
|
765
|
+
# * Array: subscribe to multiple topics with QoS 0
|
766
|
+
# * Hash: subscribe to multiple topics where the key is the topic and the value is the QoS level
|
767
|
+
#
|
768
|
+
# For example:
|
769
|
+
# packet.topics = 'a/b'
|
770
|
+
# packet.topics = ['a/b', 'c/d']
|
771
|
+
# packet.topics = [['a/b',0], ['c/d',1]]
|
772
|
+
# packet.topics = {'a/b' => 0, 'c/d' => 1}
|
773
|
+
#
|
774
|
+
def topics=(value)
|
775
|
+
# Get input into a consistent state
|
776
|
+
if value.is_a?(Array)
|
777
|
+
input = value.flatten
|
778
|
+
else
|
779
|
+
input = [value]
|
780
|
+
end
|
781
|
+
|
782
|
+
@topics = []
|
783
|
+
while(input.length>0)
|
784
|
+
item = input.shift
|
785
|
+
if item.is_a?(Hash)
|
786
|
+
# Convert hash into an ordered array of arrays
|
787
|
+
@topics += item.sort
|
788
|
+
elsif item.is_a?(String)
|
789
|
+
# Peek at the next item in the array, and remove it if it is an integer
|
790
|
+
if input.first.is_a?(Integer)
|
791
|
+
qos = input.shift
|
792
|
+
@topics << [item,qos]
|
793
|
+
else
|
794
|
+
@topics << [item,0]
|
795
|
+
end
|
796
|
+
else
|
797
|
+
# Meh?
|
798
|
+
raise "Invalid topics input: #{value.inspect}"
|
799
|
+
end
|
800
|
+
end
|
801
|
+
@topics
|
802
|
+
end
|
803
|
+
|
804
|
+
# Get serialisation of packet's body
|
805
|
+
def encode_body
|
806
|
+
if @topics.empty?
|
807
|
+
raise "no topics given when serialising packet"
|
808
|
+
end
|
809
|
+
body = encode_short(@id)
|
810
|
+
topics.each do |item|
|
811
|
+
body += encode_string(item[0])
|
812
|
+
body += encode_bytes(item[1])
|
813
|
+
end
|
814
|
+
return body
|
815
|
+
end
|
816
|
+
|
817
|
+
# Parse the body (variable header and payload) of a packet
|
818
|
+
def parse_body(buffer)
|
819
|
+
super(buffer)
|
820
|
+
@id = shift_short(buffer)
|
821
|
+
@topics = []
|
822
|
+
while(buffer.bytesize>0)
|
823
|
+
topic_name = shift_string(buffer)
|
824
|
+
topic_qos = shift_byte(buffer)
|
825
|
+
@topics << [topic_name,topic_qos]
|
826
|
+
end
|
827
|
+
end
|
828
|
+
|
829
|
+
# Check that fixed header flags are valid for this packet type
|
830
|
+
# @private
|
831
|
+
def validate_flags
|
832
|
+
if @flags != [false, true, false, false]
|
833
|
+
raise "Invalid flags in SUBSCRIBE packet header"
|
834
|
+
end
|
835
|
+
end
|
836
|
+
|
837
|
+
# Returns a human readable string, summarising the properties of the packet
|
838
|
+
def inspect
|
839
|
+
_str = "\#<#{self.class}: 0x%2.2X, %s>" % [
|
840
|
+
id,
|
841
|
+
topics.map {|t| "'#{t[0]}':#{t[1]}"}.join(', ')
|
842
|
+
]
|
843
|
+
end
|
844
|
+
end
|
845
|
+
|
846
|
+
# Class representing an MQTT Subscribe Acknowledgment packet
|
847
|
+
class Suback < PahoMqtt::Packet
|
848
|
+
# An array of return codes, ordered by the topics that were subscribed to
|
849
|
+
attr_accessor :return_codes
|
850
|
+
|
851
|
+
# Default attribute values
|
852
|
+
ATTR_DEFAULTS = {
|
853
|
+
:return_codes => [],
|
854
|
+
}
|
855
|
+
|
856
|
+
# Create a new Subscribe Acknowledgment packet
|
857
|
+
def initialize(args={})
|
858
|
+
super(ATTR_DEFAULTS.merge(args))
|
859
|
+
end
|
860
|
+
|
861
|
+
# Set the granted QoS value for each of the topics that were subscribed to
|
862
|
+
# Can either be an integer or an array or integers.
|
863
|
+
def return_codes=(value)
|
864
|
+
if value.is_a?(Array)
|
865
|
+
@return_codes = value
|
866
|
+
elsif value.is_a?(Integer)
|
867
|
+
@return_codes = [value]
|
868
|
+
else
|
869
|
+
raise "return_codes should be an integer or an array of return codes"
|
870
|
+
end
|
871
|
+
end
|
872
|
+
|
873
|
+
# Get serialisation of packet's body
|
874
|
+
def encode_body
|
875
|
+
if @return_codes.empty?
|
876
|
+
raise "no granted QoS given when serialising packet"
|
877
|
+
end
|
878
|
+
body = encode_short(@id)
|
879
|
+
return_codes.each { |qos| body += encode_bytes(qos) }
|
880
|
+
return body
|
881
|
+
end
|
882
|
+
|
883
|
+
# Parse the body (variable header and payload) of a packet
|
884
|
+
def parse_body(buffer)
|
885
|
+
super(buffer)
|
886
|
+
@id = shift_short(buffer)
|
887
|
+
while(buffer.bytesize>0)
|
888
|
+
@return_codes << shift_byte(buffer)
|
889
|
+
end
|
890
|
+
end
|
891
|
+
|
892
|
+
# Returns a human readable string, summarising the properties of the packet
|
893
|
+
def inspect
|
894
|
+
"\#<#{self.class}: 0x%2.2X, rc=%s>" % [id, return_codes.map{|rc| "0x%2.2X" % rc}.join(',')]
|
895
|
+
end
|
896
|
+
end
|
897
|
+
|
898
|
+
# Class representing an MQTT Client Unsubscribe packet
|
899
|
+
class Unsubscribe < PahoMqtt::Packet
|
900
|
+
# One or more topic paths to unsubscribe from
|
901
|
+
attr_accessor :topics
|
902
|
+
|
903
|
+
# Default attribute values
|
904
|
+
ATTR_DEFAULTS = {
|
905
|
+
:topics => [],
|
906
|
+
:flags => [false, true, false, false],
|
907
|
+
}
|
908
|
+
|
909
|
+
# Create a new Unsubscribe packet
|
910
|
+
def initialize(args={})
|
911
|
+
super(ATTR_DEFAULTS.merge(args))
|
912
|
+
end
|
913
|
+
|
914
|
+
# Set one or more topic paths to unsubscribe from
|
915
|
+
def topics=(value)
|
916
|
+
if value.is_a?(Array)
|
917
|
+
@topics = value
|
918
|
+
else
|
919
|
+
@topics = [value]
|
920
|
+
end
|
921
|
+
end
|
922
|
+
|
923
|
+
# Get serialisation of packet's body
|
924
|
+
def encode_body
|
925
|
+
if @topics.empty?
|
926
|
+
raise "no topics given when serialising packet"
|
927
|
+
end
|
928
|
+
body = encode_short(@id)
|
929
|
+
topics.each { |topic| body += encode_string(topic) }
|
930
|
+
return body
|
931
|
+
end
|
932
|
+
|
933
|
+
# Parse the body (variable header and payload) of a packet
|
934
|
+
def parse_body(buffer)
|
935
|
+
super(buffer)
|
936
|
+
@id = shift_short(buffer)
|
937
|
+
while(buffer.bytesize>0)
|
938
|
+
@topics << shift_string(buffer)
|
939
|
+
end
|
940
|
+
end
|
941
|
+
|
942
|
+
# Check that fixed header flags are valid for this packet type
|
943
|
+
# @private
|
944
|
+
def validate_flags
|
945
|
+
if @flags != [false, true, false, false]
|
946
|
+
raise "Invalid flags in UNSUBSCRIBE packet header"
|
947
|
+
end
|
948
|
+
end
|
949
|
+
|
950
|
+
# Returns a human readable string, summarising the properties of the packet
|
951
|
+
def inspect
|
952
|
+
"\#<#{self.class}: 0x%2.2X, %s>" % [
|
953
|
+
id,
|
954
|
+
topics.map {|t| "'#{t}'"}.join(', ')
|
955
|
+
]
|
956
|
+
end
|
957
|
+
end
|
958
|
+
|
959
|
+
# Class representing an MQTT Unsubscribe Acknowledgment packet
|
960
|
+
class Unsuback < PahoMqtt::Packet
|
961
|
+
# Create a new Unsubscribe Acknowledgment packet
|
962
|
+
def initialize(args={})
|
963
|
+
super(args)
|
964
|
+
end
|
965
|
+
|
966
|
+
# Get serialisation of packet's body
|
967
|
+
def encode_body
|
968
|
+
encode_short(@id)
|
969
|
+
end
|
970
|
+
|
971
|
+
# Parse the body (variable header and payload) of a packet
|
972
|
+
def parse_body(buffer)
|
973
|
+
super(buffer)
|
974
|
+
@id = shift_short(buffer)
|
975
|
+
unless buffer.empty?
|
976
|
+
raise "Extra bytes at end of Unsubscribe Acknowledgment packet"
|
977
|
+
end
|
978
|
+
end
|
979
|
+
|
980
|
+
# Returns a human readable string, summarising the properties of the packet
|
981
|
+
def inspect
|
982
|
+
"\#<#{self.class}: 0x%2.2X>" % id
|
983
|
+
end
|
984
|
+
end
|
985
|
+
|
986
|
+
# Class representing an MQTT Ping Request packet
|
987
|
+
class Pingreq < PahoMqtt::Packet
|
988
|
+
# Create a new Ping Request packet
|
989
|
+
def initialize(args={})
|
990
|
+
super(args)
|
991
|
+
end
|
992
|
+
|
993
|
+
# Check the body
|
994
|
+
def parse_body(buffer)
|
995
|
+
super(buffer)
|
996
|
+
unless buffer.empty?
|
997
|
+
raise "Extra bytes at end of Ping Request packet"
|
998
|
+
end
|
999
|
+
end
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
# Class representing an MQTT Ping Response packet
|
1003
|
+
class Pingresp < PahoMqtt::Packet
|
1004
|
+
# Create a new Ping Response packet
|
1005
|
+
def initialize(args={})
|
1006
|
+
super(args)
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
# Check the body
|
1010
|
+
def parse_body(buffer)
|
1011
|
+
super(buffer)
|
1012
|
+
unless buffer.empty?
|
1013
|
+
raise "Extra bytes at end of Ping Response packet"
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
# Class representing an MQTT Client Disconnect packet
|
1019
|
+
class Disconnect < PahoMqtt::Packet
|
1020
|
+
# Create a new Client Disconnect packet
|
1021
|
+
def initialize(args={})
|
1022
|
+
super(args)
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
# Check the body
|
1026
|
+
def parse_body(buffer)
|
1027
|
+
super(buffer)
|
1028
|
+
unless buffer.empty?
|
1029
|
+
raise "Extra bytes at end of Disconnect packet"
|
1030
|
+
end
|
1031
|
+
end
|
1032
|
+
end
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
|
1036
|
+
# An enumeration of the MQTT packet types
|
1037
|
+
PACKET_TYPES = [
|
1038
|
+
nil,
|
1039
|
+
PahoMqtt::Packet::Connect,
|
1040
|
+
PahoMqtt::Packet::Connack,
|
1041
|
+
PahoMqtt::Packet::Publish,
|
1042
|
+
PahoMqtt::Packet::Puback,
|
1043
|
+
PahoMqtt::Packet::Pubrec,
|
1044
|
+
PahoMqtt::Packet::Pubrel,
|
1045
|
+
PahoMqtt::Packet::Pubcomp,
|
1046
|
+
PahoMqtt::Packet::Subscribe,
|
1047
|
+
PahoMqtt::Packet::Suback,
|
1048
|
+
PahoMqtt::Packet::Unsubscribe,
|
1049
|
+
PahoMqtt::Packet::Unsuback,
|
1050
|
+
PahoMqtt::Packet::Pingreq,
|
1051
|
+
PahoMqtt::Packet::Pingresp,
|
1052
|
+
PahoMqtt::Packet::Disconnect,
|
1053
|
+
nil
|
1054
|
+
]
|
1055
|
+
|
1056
|
+
end
|