mini_mqtt 0.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/README.md +103 -0
- data/lib/mini_mqtt/bin_helper.rb +32 -0
- data/lib/mini_mqtt/client.rb +135 -0
- data/lib/mini_mqtt/connect_packet.rb +43 -0
- data/lib/mini_mqtt/packet.rb +239 -0
- data/lib/mini_mqtt/packet_handler.rb +111 -0
- data/lib/mini_mqtt.rb +8 -0
- data/test/client_test.rb +119 -0
- data/test/connack_packet_test.rb +18 -0
- data/test/connect_packet_test.rb +25 -0
- data/test/disconnect_packet_test.rb +13 -0
- data/test/packet_handler_test.rb +53 -0
- data/test/ping_packets_test.rb +13 -0
- data/test/puback_packet_test.rb +13 -0
- data/test/pubcomp_packet_test.rb +13 -0
- data/test/publish_packet_test.rb +44 -0
- data/test/pubrec_packet_test.rb +13 -0
- data/test/pubrel_packet_test.rb +15 -0
- data/test/suback_packet_test.rb +10 -0
- data/test/subscribe_packet_test.rb +27 -0
- data/test/test_helper.rb +17 -0
- data/test/unsuback_packet_test.rb +8 -0
- data/test/unsubscribe_packet_test.rb +22 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 998e32192be7c070e3c79ffc234c874ecf3962e7
|
4
|
+
data.tar.gz: 426b36b3850393562047e1a09f9c1cf9067fdfb9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5e86d0852c429b21a11d035bbd0da3b1d8fc1c193ee7d106fe27787f75b931167a2ed7af46cec696b471c9e3bff09714c3a75f1d8d0617cc0f2717032a05093d
|
7
|
+
data.tar.gz: dbce45001fa0d8f7ef8b8a63c6a041c630722416f9cc67bd56add6641fbf54c34e24886000bea9e66f3ae852489540a8129b6c8987414d760390604522d0d0c0
|
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# MiniMqtt
|
2
|
+
|
3
|
+
A full ruby implementation of the client side of MQTT protocol.
|
4
|
+
|
5
|
+
The philosophy behind this gem is to keep the code as minimal and tidy as possible, and to completely avoid dependencies.
|
6
|
+
|
7
|
+
cloc lib
|
8
|
+
|
9
|
+
---------------------------------------------------------------------
|
10
|
+
Language files blank comment code
|
11
|
+
---------------------------------------------------------------------
|
12
|
+
Ruby 6 103 14 449
|
13
|
+
---------------------------------------------------------------------
|
14
|
+
|
15
|
+
## Features
|
16
|
+
|
17
|
+
* Supports all MQTT features up to QoS 1.
|
18
|
+
* Doesn't support websockets.
|
19
|
+
* Doesn't store messages once the connection has been closed.
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
Clone this repo and include it to your load path. This project isn't published at rubygems yet.
|
24
|
+
|
25
|
+
## How to test
|
26
|
+
You need mosquitto server installed in order to run integration tests.
|
27
|
+
|
28
|
+
sudo apt-get install mosquitto
|
29
|
+
|
30
|
+
rake test
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'mini_mqtt'
|
36
|
+
```
|
37
|
+
|
38
|
+
### Create client instance
|
39
|
+
Possible params are host, port, user, password, keep_alive (seconds), client_id, and clean_session
|
40
|
+
client_id defaults to random client id
|
41
|
+
clean_session defaults to true
|
42
|
+
keep_alive defaults to 10
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
client = MiniMqtt::Client.new host: 'test.mosquitto.org'
|
46
|
+
```
|
47
|
+
|
48
|
+
### Establish connection
|
49
|
+
Options are will_topic, will_message, will_retain(false) and will_qos(0)
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
client.connect
|
53
|
+
```
|
54
|
+
|
55
|
+
You can check at any time if client is connected
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
puts client.connected?
|
59
|
+
```
|
60
|
+
|
61
|
+
### Publish messages
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
# Regular publish
|
65
|
+
client.publish '/topic', 'hello'
|
66
|
+
|
67
|
+
# Publish with retain
|
68
|
+
client.publish '/other_topic', 'retained_message', retain: true
|
69
|
+
|
70
|
+
# Publish with qos
|
71
|
+
client.publish '/qos_topic', 'message', qos: 1
|
72
|
+
```
|
73
|
+
|
74
|
+
### Subscribe to topics
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
# Single topic
|
78
|
+
client.subscribe '/topic'
|
79
|
+
|
80
|
+
# Multiple topics
|
81
|
+
client.subscribe '/topic', '/other_topic'
|
82
|
+
|
83
|
+
# Specifying max qos for topic (default 0)
|
84
|
+
client.subscribe '/topic', '/qos_topic' => 1
|
85
|
+
```
|
86
|
+
|
87
|
+
### Get messages
|
88
|
+
The caller of these methods are blocked until a message arrives, or the connection is lost.
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
# Get a single message
|
92
|
+
msg = client.get_message
|
93
|
+
puts msg.message, msg.topic, msg.qos, msg.retain, msg.packet_id, msg.dup
|
94
|
+
|
95
|
+
# Get messages in an infinite loop. Breaks if connection is lost.
|
96
|
+
client.get_messages do |msg, topic|
|
97
|
+
puts "Received #{ msg } on topic #{ topic }"
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
### Gracefully disconnect
|
102
|
+
client.disconnect
|
103
|
+
```
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module BinHelper
|
2
|
+
def uchar number
|
3
|
+
[number].pack 'C'
|
4
|
+
end
|
5
|
+
|
6
|
+
def ushort number
|
7
|
+
[number].pack 'n'
|
8
|
+
end
|
9
|
+
|
10
|
+
def flag_byte flags
|
11
|
+
raise "flags must have 8 elements" unless flags.size == 8
|
12
|
+
byte = 0
|
13
|
+
flags.reverse.each_with_index do |flag, index|
|
14
|
+
byte |= 1 << index if flag && flag != 0
|
15
|
+
end
|
16
|
+
byte
|
17
|
+
end
|
18
|
+
|
19
|
+
def mqtt_utf8_encode string
|
20
|
+
ushort(string.length) + string
|
21
|
+
end
|
22
|
+
|
23
|
+
def read_mqtt_encoded_string stream
|
24
|
+
length = read_ushort stream
|
25
|
+
stream.read length
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_ushort stream
|
29
|
+
stream.read(2).unpack('n').first
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module MiniMqtt
|
4
|
+
class Client
|
5
|
+
attr_accessor :host, :port, :user, :password, :clean_session, :client_id
|
6
|
+
|
7
|
+
def initialize params = {}
|
8
|
+
@host = params[:host] || 'localhost'
|
9
|
+
@port = params[:port] || 1883
|
10
|
+
@user = params[:user]
|
11
|
+
@password = params[:password]
|
12
|
+
@keep_alive = params[:keep_alive] || 10
|
13
|
+
@client_id = params[:client_id] || generate_client_id
|
14
|
+
@clean_session = params.fetch :clean_session, true
|
15
|
+
end
|
16
|
+
|
17
|
+
def connect options = {}
|
18
|
+
# Create socket and packet handler
|
19
|
+
@socket = TCPSocket.new @host, @port
|
20
|
+
@packet_handler = PacketHandler.new @socket
|
21
|
+
|
22
|
+
# Send ConnectPacket
|
23
|
+
send_packet ConnectPacket.new user: @user,
|
24
|
+
password: @password, keep_alive: @keep_alive, client_id: @client_id,
|
25
|
+
clean_session: @clean_session, will_topic: options[:will_topic],
|
26
|
+
will_message: options[:will_message], will_retain: options[:will_retain]
|
27
|
+
|
28
|
+
# Receive connack packet
|
29
|
+
connack = receive_packet
|
30
|
+
|
31
|
+
if connack.accepted?
|
32
|
+
@received_messages = Queue.new
|
33
|
+
@last_ping_response = Time.now
|
34
|
+
spawn_read_thread!
|
35
|
+
spawn_keepalive_thread!
|
36
|
+
else
|
37
|
+
raise StandardError.new(connack.error)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def subscribe *params
|
42
|
+
# Each param can be a topic or a topic with its max qos.
|
43
|
+
# Example: subscribe 'topic1', 'topic2' => 1
|
44
|
+
topics = params.map do |arg|
|
45
|
+
arg.is_a?(Hash) ? arg : { arg => 0 }
|
46
|
+
end
|
47
|
+
topics = topics.inject :merge
|
48
|
+
packet = SubscribePacket.new topics: topics
|
49
|
+
send_packet packet
|
50
|
+
end
|
51
|
+
|
52
|
+
def unsubscribe *topics
|
53
|
+
send_packet UnsubscribePacket.new topics: topics
|
54
|
+
end
|
55
|
+
|
56
|
+
def publish topic, message, options = {}
|
57
|
+
packet = PublishPacket.new topic: topic, message: message.to_s,
|
58
|
+
retain: options[:retain], qos: options[:qos]
|
59
|
+
send_packet packet
|
60
|
+
end
|
61
|
+
|
62
|
+
def disconnect
|
63
|
+
# Send DisconnectPacket, then kill threads and close socket
|
64
|
+
send_packet DisconnectPacket.new
|
65
|
+
@read_thread.kill
|
66
|
+
@keepalive_thread.kill
|
67
|
+
@socket.close
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_message
|
71
|
+
@received_messages.pop
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_messages
|
75
|
+
while message = get_message
|
76
|
+
yield message.message, message.topic
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def connected?
|
81
|
+
@socket && !@socket.closed?
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def send_packet packet
|
87
|
+
@packet_handler.write_packet packet
|
88
|
+
end
|
89
|
+
|
90
|
+
def receive_packet
|
91
|
+
@packet_handler.get_packet
|
92
|
+
end
|
93
|
+
|
94
|
+
def handle_received_packet packet
|
95
|
+
case packet
|
96
|
+
when PingrespPacket
|
97
|
+
@last_ping_response = Time.now
|
98
|
+
|
99
|
+
when PublishPacket
|
100
|
+
@received_messages << packet
|
101
|
+
if packet.qos > 0
|
102
|
+
send_packet PubackPacket.new packet_id: packet.packet_id
|
103
|
+
end
|
104
|
+
|
105
|
+
when PubackPacket
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def generate_client_id
|
110
|
+
"client_#{ rand(10000000) }"
|
111
|
+
end
|
112
|
+
|
113
|
+
def spawn_read_thread!
|
114
|
+
@read_thread = Thread.new do
|
115
|
+
while connected? do
|
116
|
+
handle_received_packet receive_packet
|
117
|
+
end
|
118
|
+
@received_messages << nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def spawn_keepalive_thread!
|
123
|
+
@keepalive_thread = Thread.new do
|
124
|
+
while connected? do
|
125
|
+
send_packet PingreqPacket.new
|
126
|
+
sleep @keep_alive
|
127
|
+
if Time.now - @last_ping_response > 2 * @keep_alive
|
128
|
+
puts "Error: MQTT Server not responding to ping. Disconnecting."
|
129
|
+
@socket.close
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module MiniMqtt
|
2
|
+
class ConnectPacket < Packet
|
3
|
+
def initialize options = {}
|
4
|
+
@user = options[:user]
|
5
|
+
@password = options[:password]
|
6
|
+
@client_id = options[:client_id]
|
7
|
+
@will_message = options[:will_message]
|
8
|
+
@will_topic = options[:will_topic]
|
9
|
+
@will_retain = options[:will_retain]
|
10
|
+
@will_qos = options[:will_qos] || 0
|
11
|
+
@clean_session = options[:clean_session]
|
12
|
+
@keep_alive = options[:keep_alive]
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_variable_header
|
16
|
+
# Protocol name
|
17
|
+
header = ushort(4) # length of name
|
18
|
+
header << 'MQTT' # name
|
19
|
+
# Protocol level
|
20
|
+
header << uchar(4)
|
21
|
+
# Flags
|
22
|
+
byte = flag_byte [ @user, @password, @will_retain, nil, nil,
|
23
|
+
@will_message, @clean_session, nil]
|
24
|
+
byte |= @will_qos << 3
|
25
|
+
header << uchar(byte)
|
26
|
+
#Keepalive
|
27
|
+
header << ushort(@keep_alive)
|
28
|
+
header
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_payload
|
32
|
+
payload = ""
|
33
|
+
payload << mqtt_utf8_encode(@client_id)
|
34
|
+
if @will_message
|
35
|
+
payload << mqtt_utf8_encode(@will_topic)
|
36
|
+
payload << mqtt_utf8_encode(@will_message)
|
37
|
+
end
|
38
|
+
payload << mqtt_utf8_encode(@user) if @user
|
39
|
+
payload << mqtt_utf8_encode(@password) if @password
|
40
|
+
payload
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
class InvalidFlagsError < StandardError ; end
|
2
|
+
|
3
|
+
module MiniMqtt
|
4
|
+
class Packet
|
5
|
+
include BinHelper
|
6
|
+
|
7
|
+
@@last_packet_id = 0
|
8
|
+
|
9
|
+
def decode stream, flags = 0
|
10
|
+
@stream = stream
|
11
|
+
handle_flags flags
|
12
|
+
read_variable_header
|
13
|
+
read_payload
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def encode
|
18
|
+
build_variable_header + build_payload
|
19
|
+
end
|
20
|
+
|
21
|
+
def flags
|
22
|
+
0b000
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def new_packet_id
|
28
|
+
@@last_packet_id += 1
|
29
|
+
@@last_packet_id %= 65535
|
30
|
+
1 + @@last_packet_id
|
31
|
+
end
|
32
|
+
|
33
|
+
def read_variable_header
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_payload
|
37
|
+
end
|
38
|
+
|
39
|
+
def handle_flags flags
|
40
|
+
end
|
41
|
+
|
42
|
+
def build_variable_header
|
43
|
+
""
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_payload
|
47
|
+
""
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
module AckPacket
|
53
|
+
attr_accessor :packet_id
|
54
|
+
|
55
|
+
def initialize params = {}
|
56
|
+
@packet_id = params[:packet_id]
|
57
|
+
end
|
58
|
+
|
59
|
+
def read_variable_header
|
60
|
+
@packet_id = read_ushort @stream
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_variable_header
|
64
|
+
ushort @packet_id
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class ConnackPacket < Packet
|
69
|
+
attr_reader :return_code
|
70
|
+
|
71
|
+
ERRORS = { 1 => "unacceptable protocol version",
|
72
|
+
2 => "identifier rejected",
|
73
|
+
3 => "server unavailable",
|
74
|
+
4 => "bad username or password",
|
75
|
+
5 => "not authorized" }
|
76
|
+
|
77
|
+
|
78
|
+
def read_variable_header
|
79
|
+
@session_present = @stream.readbyte & 0x01
|
80
|
+
@return_code = @stream.readbyte
|
81
|
+
end
|
82
|
+
|
83
|
+
def session_present?
|
84
|
+
@session_present == 1
|
85
|
+
end
|
86
|
+
|
87
|
+
def accepted?
|
88
|
+
@return_code == 0
|
89
|
+
end
|
90
|
+
|
91
|
+
def error_message
|
92
|
+
ERRORS[@return_code]
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
class PublishPacket < Packet
|
98
|
+
attr_accessor :dup, :qos, :retain, :packet_id, :topic, :message
|
99
|
+
|
100
|
+
def handle_flags flags
|
101
|
+
@dup = flags & 0b1000 != 0
|
102
|
+
@qos = (flags & 0b0110) >> 1
|
103
|
+
@retain = flags & 0b0001 != 0
|
104
|
+
end
|
105
|
+
|
106
|
+
def read_variable_header
|
107
|
+
@topic = read_mqtt_encoded_string @stream
|
108
|
+
if @qos > 0
|
109
|
+
@packet_id = read_ushort @stream
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def read_payload
|
114
|
+
@message = @stream.read
|
115
|
+
end
|
116
|
+
|
117
|
+
def initialize params = {}
|
118
|
+
@topic = params[:topic] || ""
|
119
|
+
@message = params[:message] || ""
|
120
|
+
@qos = params[:qos] || 0
|
121
|
+
@dup = params[:dup] || false
|
122
|
+
@retain = params[:retain] || false
|
123
|
+
@packet_id = params[:packet_id]
|
124
|
+
end
|
125
|
+
|
126
|
+
def flags
|
127
|
+
flags = 0
|
128
|
+
flags |= 0b0001 if @retain
|
129
|
+
flags |= qos << 1
|
130
|
+
flags |= 0b1000 if @dup
|
131
|
+
flags
|
132
|
+
end
|
133
|
+
|
134
|
+
def build_variable_header
|
135
|
+
header = mqtt_utf8_encode @topic
|
136
|
+
if @qos > 0
|
137
|
+
@packet_id ||= new_packet_id
|
138
|
+
header << ushort(@packet_id)
|
139
|
+
end
|
140
|
+
header
|
141
|
+
end
|
142
|
+
|
143
|
+
def build_payload
|
144
|
+
@message
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
class PubackPacket < Packet
|
150
|
+
include AckPacket
|
151
|
+
end
|
152
|
+
|
153
|
+
class PubrecPacket < Packet
|
154
|
+
include AckPacket
|
155
|
+
end
|
156
|
+
|
157
|
+
class PubrelPacket < Packet
|
158
|
+
include AckPacket
|
159
|
+
|
160
|
+
def flags
|
161
|
+
0b0010
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class PubcompPacket < Packet
|
166
|
+
include AckPacket
|
167
|
+
end
|
168
|
+
|
169
|
+
class SubscribePacket < Packet
|
170
|
+
attr_accessor :packet_id, :topics
|
171
|
+
|
172
|
+
def initialize params = {}
|
173
|
+
@topics = params[:topics]
|
174
|
+
end
|
175
|
+
|
176
|
+
def flags
|
177
|
+
0b0010
|
178
|
+
end
|
179
|
+
|
180
|
+
def build_variable_header
|
181
|
+
@packet_id ||= new_packet_id
|
182
|
+
ushort @packet_id
|
183
|
+
end
|
184
|
+
|
185
|
+
def build_payload
|
186
|
+
@topics.map do |topic, qos|
|
187
|
+
mqtt_utf8_encode(topic) + uchar(qos)
|
188
|
+
end.join
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class SubackPacket < Packet
|
193
|
+
attr_accessor :packet_id, :max_qos_accepted
|
194
|
+
|
195
|
+
def read_variable_header
|
196
|
+
@packet_id = read_ushort @stream
|
197
|
+
end
|
198
|
+
|
199
|
+
def read_payload
|
200
|
+
@max_qos_accepted = @stream.read.unpack 'C*'
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class UnsubscribePacket < Packet
|
205
|
+
attr_accessor :packet_id, :topics
|
206
|
+
|
207
|
+
def flags
|
208
|
+
0b0010
|
209
|
+
end
|
210
|
+
|
211
|
+
def initialize params = {}
|
212
|
+
@topics = params[:topics]
|
213
|
+
end
|
214
|
+
|
215
|
+
def build_variable_header
|
216
|
+
@packet_id ||= new_packet_id
|
217
|
+
ushort @packet_id
|
218
|
+
end
|
219
|
+
|
220
|
+
def build_payload
|
221
|
+
@topics.map do |topic|
|
222
|
+
mqtt_utf8_encode(topic)
|
223
|
+
end.join
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class UnsubackPacket < Packet
|
228
|
+
include AckPacket
|
229
|
+
end
|
230
|
+
|
231
|
+
class PingreqPacket < Packet
|
232
|
+
end
|
233
|
+
|
234
|
+
class PingrespPacket < Packet
|
235
|
+
end
|
236
|
+
|
237
|
+
class DisconnectPacket < Packet
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module MiniMqtt
|
4
|
+
PACKET_CLASSES = { 1 => ConnectPacket,
|
5
|
+
2 => ConnackPacket,
|
6
|
+
3 => PublishPacket,
|
7
|
+
4 => PubackPacket,
|
8
|
+
5 => PubrecPacket,
|
9
|
+
6 => PubrelPacket,
|
10
|
+
7 => PubcompPacket,
|
11
|
+
8 => SubscribePacket,
|
12
|
+
9 => SubackPacket,
|
13
|
+
10 => UnsubscribePacket,
|
14
|
+
11 => UnsubackPacket,
|
15
|
+
12 => PingreqPacket,
|
16
|
+
13 => PingrespPacket,
|
17
|
+
14 => DisconnectPacket }
|
18
|
+
PACKET_CODES = PACKET_CLASSES.invert
|
19
|
+
|
20
|
+
class PacketHandler
|
21
|
+
include BinHelper
|
22
|
+
|
23
|
+
MAX_LENGTH_MULTIPLIER = 128 ** 3
|
24
|
+
|
25
|
+
@@debug = false
|
26
|
+
|
27
|
+
def self.enable_debug
|
28
|
+
@@debug = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize stream
|
32
|
+
@stream = stream
|
33
|
+
@mutex = Mutex.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_packet
|
37
|
+
# First byte contains packet type and flags. 4 bits each.
|
38
|
+
first_byte = @stream.readbyte
|
39
|
+
packet_class = PACKET_CLASSES[ first_byte >> 4 ]
|
40
|
+
flags = first_byte & 0xf
|
41
|
+
|
42
|
+
#Decode length using algorithm, and read packet body.
|
43
|
+
length = decode_length @stream
|
44
|
+
encoded_packet = @stream.read length
|
45
|
+
log_in_packet packet_class, encoded_packet
|
46
|
+
|
47
|
+
# Create appropiate packet instance and decode the packet body.
|
48
|
+
packet_class.new.decode StringIO.new(encoded_packet), flags
|
49
|
+
|
50
|
+
rescue StandardError => e
|
51
|
+
log "Exception while receiving: #{ e.inspect }"
|
52
|
+
@stream.close
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_packet packet
|
56
|
+
# Write type and flags, then encoded packet length, then packet
|
57
|
+
@mutex.synchronize do
|
58
|
+
type_and_flags = PACKET_CODES[packet.class] << 4
|
59
|
+
type_and_flags += packet.flags
|
60
|
+
@stream.write uchar(type_and_flags)
|
61
|
+
encoded_packet = packet.encode
|
62
|
+
log_out_packet packet
|
63
|
+
@stream.write encode_length(encoded_packet.length)
|
64
|
+
@stream.write encoded_packet
|
65
|
+
end
|
66
|
+
rescue StandardError => e
|
67
|
+
log "Exception while receiving: #{ e.inspect }"
|
68
|
+
@stream.close
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def encode_length length
|
74
|
+
encoded = ""
|
75
|
+
loop do
|
76
|
+
encoded_byte = length % 128
|
77
|
+
length = length / 128
|
78
|
+
encoded_byte |= 128 if length > 0
|
79
|
+
encoded << encoded_byte.chr
|
80
|
+
break if length == 0
|
81
|
+
end
|
82
|
+
encoded
|
83
|
+
end
|
84
|
+
|
85
|
+
def decode_length stream
|
86
|
+
length = 0
|
87
|
+
multiplier = 1
|
88
|
+
while encoded_byte = stream.readbyte
|
89
|
+
length += (encoded_byte & 0x7f) * multiplier
|
90
|
+
break if encoded_byte & 0x80 == 0
|
91
|
+
multiplier *= 128
|
92
|
+
raise "Malformed remaining length" if multiplier > MAX_LENGTH_MULTIPLIER
|
93
|
+
end
|
94
|
+
length
|
95
|
+
end
|
96
|
+
|
97
|
+
def log_in_packet type, message
|
98
|
+
log "\nIN - #{ type } - #{ message.inspect }\n"
|
99
|
+
end
|
100
|
+
|
101
|
+
def log_out_packet packet
|
102
|
+
log "\nOUT - #{ packet.class } - #{ packet.instance_variable_get :@packet_id } - #{ packet.encode.inspect }\n"
|
103
|
+
end
|
104
|
+
|
105
|
+
def log text
|
106
|
+
if @@debug
|
107
|
+
puts text
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/mini_mqtt.rb
ADDED
data/test/client_test.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'socket'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
class ClientTest < MiniTest::Test
|
6
|
+
TEST_SERVER = 'test.mosquitto.org'
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@client = Client.new host: TEST_SERVER
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_mosquitto_server_is_running
|
13
|
+
begin
|
14
|
+
socket = TCPSocket.new TEST_SERVER, 1883
|
15
|
+
socket.close
|
16
|
+
rescue
|
17
|
+
puts "There should be a mosquitto server running to run tests."
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_connect_and_disconnect
|
22
|
+
@client.connect
|
23
|
+
assert @client.connected?
|
24
|
+
@client.disconnect
|
25
|
+
refute @client.connected?
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_subscribe_and_publish
|
29
|
+
@client.connect
|
30
|
+
@client.subscribe '/mini_mqtt/test'
|
31
|
+
@client.publish '/mini_mqtt/test', 'hi'
|
32
|
+
msg = @client.get_message
|
33
|
+
assert_equal '/mini_mqtt/test', msg.topic
|
34
|
+
assert_equal 'hi', msg.message
|
35
|
+
@client.disconnect
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_subscribe_multiple_topics
|
39
|
+
@client.connect
|
40
|
+
@client.subscribe '/mini_mqtt/test1', '/mini_mqtt/test2'
|
41
|
+
@client.publish '/mini_mqtt/test1', 'message_1'
|
42
|
+
@client.publish '/mini_mqtt/test2', 'message_2'
|
43
|
+
expected = ['message_1', 'message_2']
|
44
|
+
@client.get_messages do |message|
|
45
|
+
assert_equal expected.shift, message
|
46
|
+
break if expected.empty?
|
47
|
+
end
|
48
|
+
@client.disconnect
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_unsubscribe
|
52
|
+
@client.connect
|
53
|
+
@client.subscribe '/mini_mqtt/test'
|
54
|
+
@client.unsubscribe '/mini_mqtt/test'
|
55
|
+
@client.publish '/mini_mqtt/test', 'hi'
|
56
|
+
assert_raises(Timeout::Error) do
|
57
|
+
Timeout::timeout(1) do
|
58
|
+
@client.get_message
|
59
|
+
end
|
60
|
+
end
|
61
|
+
@client.disconnect
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_retain_message
|
65
|
+
@client.connect
|
66
|
+
message_to_retain = rand.to_s
|
67
|
+
@client.publish '/mini_mqtt/retain', message_to_retain, retain: true
|
68
|
+
@client.subscribe '/mini_mqtt/retain'
|
69
|
+
assert_equal message_to_retain, @client.get_message.message
|
70
|
+
@client.disconnect
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_clean_session
|
74
|
+
@client = MiniMqtt::Client.new host: TEST_SERVER, clean_session: false
|
75
|
+
@client.connect
|
76
|
+
@client.subscribe '/mini_mqtt/test'
|
77
|
+
@client.disconnect
|
78
|
+
@client.connect
|
79
|
+
@client.publish '/mini_mqtt/test', 'hello'
|
80
|
+
assert_equal 'hello', @client.get_message.message
|
81
|
+
@client.disconnect
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_last_will
|
85
|
+
@client2 = MiniMqtt::Client.new host: TEST_SERVER
|
86
|
+
@client2.connect
|
87
|
+
@client2.subscribe 'mini_mqtt/last_will'
|
88
|
+
sleep 3 # wait for suback to come back
|
89
|
+
@client.connect will_topic: 'mini_mqtt/last_will', will_message: 'help!!'
|
90
|
+
# abruptly close connection by closing socket.
|
91
|
+
@client.instance_variable_get(:@socket).close
|
92
|
+
assert_equal 'help!!', @client2.get_message.message
|
93
|
+
@client2.disconnect
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_last_will_with_retain
|
97
|
+
will_msg = rand.to_s
|
98
|
+
@client.connect will_topic: 'mini_mqtt/last_will_retain', will_message: will_msg,
|
99
|
+
will_retain: true
|
100
|
+
assert @client.connected?
|
101
|
+
@client.instance_variable_get(:@socket).close
|
102
|
+
@client.connect
|
103
|
+
@client.subscribe 'mini_mqtt/last_will_retain'
|
104
|
+
assert_equal will_msg, @client.get_message.message
|
105
|
+
@client.disconnect
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_qos_1
|
109
|
+
@client.connect
|
110
|
+
@client.subscribe '/mini_mqtt/topic_qos' => 1
|
111
|
+
@client.publish '/mini_mqtt/topic_qos', 'msg', qos: 1
|
112
|
+
msg = @client.get_message
|
113
|
+
assert_equal '/mini_mqtt/topic_qos', msg.topic
|
114
|
+
assert_equal 'msg', msg.message
|
115
|
+
assert_equal 1, msg.qos
|
116
|
+
@client.disconnect
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ConnackPacketTest < MiniTest::Test
|
4
|
+
def test_decode_accepted_connection
|
5
|
+
packet = ConnackPacket.new
|
6
|
+
packet.decode "\x01\x00".to_stream
|
7
|
+
assert packet.session_present?
|
8
|
+
assert packet.accepted?
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_decode_refused_connection
|
12
|
+
packet = ConnackPacket.new
|
13
|
+
packet.decode "\x00\x04".to_stream
|
14
|
+
assert_equal false, packet.session_present?
|
15
|
+
assert_equal false, packet.accepted?
|
16
|
+
assert_match(/bad username or password/, packet.error_message)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ConnectPacketTest < MiniTest::Test
|
4
|
+
def test_encode_with_all_params
|
5
|
+
packet = ConnectPacket.new user: 'arman',
|
6
|
+
password: 'secret',
|
7
|
+
client_id: 'abc',
|
8
|
+
will_message: 'I am dead',
|
9
|
+
will_topic: 'last_will',
|
10
|
+
will_retain: false,
|
11
|
+
will_qos: 0,
|
12
|
+
clean_session: true,
|
13
|
+
keep_alive: 20
|
14
|
+
encoded = packet.encode
|
15
|
+
assert_equal "\x00\x04MQTT", encoded[0..5]
|
16
|
+
assert_equal "\x04", encoded[6]
|
17
|
+
assert_equal [0xc6], encoded[7].bytes
|
18
|
+
assert_equal "\x00\x14", encoded[8..9]
|
19
|
+
assert_equal "\x00\x03abc", encoded[10..14]
|
20
|
+
assert_equal "\x00\x09last_will", encoded[15..25]
|
21
|
+
assert_equal "\x00\x09I am dead", encoded[26..36]
|
22
|
+
assert_equal "\x00\x05arman", encoded[37..43]
|
23
|
+
assert_equal "\x00\x06secret", encoded[44..-1]
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class DisconnectPacketTest < MiniTest::Test
|
4
|
+
def test_encode
|
5
|
+
packet = DisconnectPacket.new
|
6
|
+
assert_equal "", packet.encode
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_flags
|
10
|
+
packet = DisconnectPacket.new
|
11
|
+
assert_equal 0, packet.flags
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestPacketHandler < Minitest::Test
|
4
|
+
def setup
|
5
|
+
@socket = StringIO.new
|
6
|
+
@handler = PacketHandler.new @socket
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_get_packet
|
10
|
+
@socket.write "\xd0\x00"
|
11
|
+
@socket.write "\xa2\x05topic"
|
12
|
+
@socket.rewind
|
13
|
+
packet = @handler.get_packet
|
14
|
+
assert_equal PingrespPacket, packet.class
|
15
|
+
|
16
|
+
packet = @handler.get_packet
|
17
|
+
assert_equal UnsubscribePacket, packet.class
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_write_pingreq_packet
|
21
|
+
packet = PingreqPacket.new
|
22
|
+
@handler.write_packet packet
|
23
|
+
@socket.rewind
|
24
|
+
assert_equal 0xc0, @socket.readbyte # packet type and flags
|
25
|
+
assert_equal 0x00, @socket.readbyte # encoded length
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_write_connect_packet
|
29
|
+
packet = ConnectPacket.new client_id: 'abc', keep_alive: 15
|
30
|
+
@handler.write_packet packet
|
31
|
+
@socket.rewind
|
32
|
+
assert_equal 0x10, @socket.readbyte # packet type and flags
|
33
|
+
assert_equal 0x0F, @socket.readbyte # encoded length
|
34
|
+
assert_equal @socket.read, packet.encode
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_length_encoding
|
38
|
+
assert_equal [0x00], @handler.send(:encode_length, 0).bytes
|
39
|
+
assert_equal [0xc1,0x02], @handler.send(:encode_length, 321).bytes
|
40
|
+
assert_equal [0x80,0x80,0x01], @handler.send(:encode_length, 16384).bytes
|
41
|
+
assert_equal [0xff,0xff,0xff,0x7f], @handler.send(:encode_length, 268_435_455).bytes
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_length_decoding
|
45
|
+
assert_equal 0, @handler.send(:decode_length, "\x00".to_stream)
|
46
|
+
assert_equal 321, @handler.send(:decode_length, "\xC1\x02".to_stream)
|
47
|
+
assert_equal 16384, @handler.send(:decode_length, "\x80\x80\x01".to_stream)
|
48
|
+
assert_equal 268_435_455, @handler.send(:decode_length, "\xFF\xFF\xFF\x7F".to_stream)
|
49
|
+
assert_raises StandardError do
|
50
|
+
@handler.send(:decode_length, "\xFF\xFF\xFF\xFF".to_stream)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PingPacketTest < MiniTest::Test
|
4
|
+
def test_pingreq_packet
|
5
|
+
packet = PingreqPacket.new
|
6
|
+
assert_equal "", packet.encode
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_pingresp_packet
|
10
|
+
packet = PingrespPacket.new.decode ""
|
11
|
+
assert packet
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PubackPacketTest < MiniTest::Test
|
4
|
+
def test_decode
|
5
|
+
puback = PubackPacket.new.decode "\x00\xFF".to_stream
|
6
|
+
assert_equal puback.packet_id, 0xFF
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_encode
|
10
|
+
puback = PubackPacket.new packet_id: 9
|
11
|
+
assert_equal puback.encode, "\x00\x09"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PubcompPacketTest < MiniTest::Test
|
4
|
+
def test_decode
|
5
|
+
pubcomp = PubcompPacket.new.decode "\x00\xFF".to_stream
|
6
|
+
assert_equal pubcomp.packet_id, 0xFF
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_encode
|
10
|
+
pubcomp = PubcompPacket.new packet_id: 9
|
11
|
+
assert_equal pubcomp.encode, "\x00\x09"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PublishPacketTest < MiniTest::Test
|
4
|
+
def setup
|
5
|
+
@inbound_publish_1 = PublishPacket.new.decode "\x00\x03a/b\x00\x0aMessageHere".to_stream, 0b1011
|
6
|
+
@inbound_publish_2 = PublishPacket.new.decode "\x00\x03a/bMessageHere".to_stream, 0b0000
|
7
|
+
@outbound_publish_1 = PublishPacket.new topic: 'help', message: 'SOS'
|
8
|
+
@outbound_publish_2 = PublishPacket.new topic: 'help', message: 'SOS',
|
9
|
+
qos: 1, dup: true, retain: true, packet_id: 5
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_read_flags
|
13
|
+
assert @inbound_publish_1.dup
|
14
|
+
assert @inbound_publish_1.retain
|
15
|
+
assert_equal 1, @inbound_publish_1.qos
|
16
|
+
|
17
|
+
refute @inbound_publish_2.dup
|
18
|
+
refute @inbound_publish_2.retain
|
19
|
+
assert_equal 0, @inbound_publish_2.qos
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_decode_topic_and_packet_id
|
23
|
+
assert_equal 'a/b', @inbound_publish_1.topic
|
24
|
+
assert_equal 10, @inbound_publish_1.packet_id
|
25
|
+
|
26
|
+
assert_equal 'a/b', @inbound_publish_2.topic
|
27
|
+
assert_equal nil, @inbound_publish_2.packet_id
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_decode_message
|
31
|
+
assert_equal 'MessageHere', @inbound_publish_1.message
|
32
|
+
assert_equal 'MessageHere', @inbound_publish_2.message
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_encode_flags
|
36
|
+
assert_equal 0b0000, @outbound_publish_1.flags
|
37
|
+
assert_equal 0b1011, @outbound_publish_2.flags
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_encode_message
|
41
|
+
assert_equal "\x00\x04helpSOS", @outbound_publish_1.encode
|
42
|
+
assert_equal "\x00\x04help\x00\x05SOS", @outbound_publish_2.encode
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PubrecPacketTest < MiniTest::Test
|
4
|
+
def test_decode
|
5
|
+
pubrec = PubrecPacket.new.decode "\x00\xFF".to_stream
|
6
|
+
assert_equal pubrec.packet_id, 0xFF
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_encode
|
10
|
+
pubrec = PubrecPacket.new packet_id: 9
|
11
|
+
assert_equal pubrec.encode, "\x00\x09"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PubrelPacketTest < MiniTest::Test
|
4
|
+
def test_decode
|
5
|
+
pubrel = PubrelPacket.new.decode "\x00\xFF".to_stream
|
6
|
+
assert_equal pubrel.packet_id, 0xFF
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_encode_and_flags
|
10
|
+
pubrel = PubrelPacket.new packet_id: 9
|
11
|
+
assert_equal pubrel.encode, "\x00\x09"
|
12
|
+
assert_equal 0b0010, pubrel.flags
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SubscribePacketTest < MiniTest::Test
|
4
|
+
def setup
|
5
|
+
@packet = SubscribePacket.new topics: { 'topic1' => 0, 'topic2' => 1 }
|
6
|
+
@packet2 = SubscribePacket.new topics: { 'topic3' => 0 }
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_flags
|
10
|
+
assert_equal 0b0010, @packet.flags
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_packet_ids
|
14
|
+
@packet.encode
|
15
|
+
@packet2.encode
|
16
|
+
assert @packet.packet_id > 0
|
17
|
+
assert @packet2.packet_id > @packet.packet_id
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_encode
|
21
|
+
@packet.packet_id = 1
|
22
|
+
@packet2.packet_id = 2
|
23
|
+
assert_equal "\x00\x01\x00\x06topic1\x00\x00\x06topic2\x01", @packet.encode
|
24
|
+
assert_equal "\x00\x02\x00\x06topic3\x00", @packet2.encode
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
$:.unshift File.join( __FILE__, "..", "..", "lib")
|
5
|
+
require 'pry'
|
6
|
+
require 'mini_mqtt'
|
7
|
+
require 'minitest/autorun'
|
8
|
+
|
9
|
+
class String
|
10
|
+
def to_stream
|
11
|
+
StringIO.new self
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class MiniTest::Test
|
16
|
+
include MiniMqtt
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class UnsubscribePacketTest < MiniTest::Test
|
4
|
+
def setup
|
5
|
+
@packet = UnsubscribePacket.new topics: ['topic1', 'topic2']
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_flags
|
9
|
+
assert_equal 0b0010, @packet.flags
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_packet_id
|
13
|
+
@packet.encode
|
14
|
+
assert @packet.packet_id > 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_encode
|
18
|
+
@packet.packet_id = 1
|
19
|
+
assert_equal "\x00\x01\x00\x06topic1\x00\x06topic2", @packet.encode
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mini_mqtt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Armando Andini
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.0.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.0.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.1.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.1.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.1.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.1.0
|
83
|
+
description: Minimalist implementation of MQTT client purely in Ruby.
|
84
|
+
email: armando.andini@hotmail.com
|
85
|
+
executables: []
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- README.md
|
90
|
+
- lib/mini_mqtt.rb
|
91
|
+
- lib/mini_mqtt/bin_helper.rb
|
92
|
+
- lib/mini_mqtt/client.rb
|
93
|
+
- lib/mini_mqtt/connect_packet.rb
|
94
|
+
- lib/mini_mqtt/packet.rb
|
95
|
+
- lib/mini_mqtt/packet_handler.rb
|
96
|
+
- test/client_test.rb
|
97
|
+
- test/connack_packet_test.rb
|
98
|
+
- test/connect_packet_test.rb
|
99
|
+
- test/disconnect_packet_test.rb
|
100
|
+
- test/packet_handler_test.rb
|
101
|
+
- test/ping_packets_test.rb
|
102
|
+
- test/puback_packet_test.rb
|
103
|
+
- test/pubcomp_packet_test.rb
|
104
|
+
- test/publish_packet_test.rb
|
105
|
+
- test/pubrec_packet_test.rb
|
106
|
+
- test/pubrel_packet_test.rb
|
107
|
+
- test/suback_packet_test.rb
|
108
|
+
- test/subscribe_packet_test.rb
|
109
|
+
- test/test_helper.rb
|
110
|
+
- test/unsuback_packet_test.rb
|
111
|
+
- test/unsubscribe_packet_test.rb
|
112
|
+
homepage: http://github.com/antico5/mini_mqtt
|
113
|
+
licenses:
|
114
|
+
- MIT
|
115
|
+
metadata: {}
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options: []
|
118
|
+
require_paths:
|
119
|
+
- lib
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
requirements: []
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 2.5.1
|
133
|
+
signing_key:
|
134
|
+
specification_version: 4
|
135
|
+
summary: MQTT Client for Ruby.
|
136
|
+
test_files:
|
137
|
+
- test/pubcomp_packet_test.rb
|
138
|
+
- test/disconnect_packet_test.rb
|
139
|
+
- test/publish_packet_test.rb
|
140
|
+
- test/ping_packets_test.rb
|
141
|
+
- test/connack_packet_test.rb
|
142
|
+
- test/unsubscribe_packet_test.rb
|
143
|
+
- test/connect_packet_test.rb
|
144
|
+
- test/test_helper.rb
|
145
|
+
- test/subscribe_packet_test.rb
|
146
|
+
- test/unsuback_packet_test.rb
|
147
|
+
- test/suback_packet_test.rb
|
148
|
+
- test/puback_packet_test.rb
|
149
|
+
- test/client_test.rb
|
150
|
+
- test/pubrel_packet_test.rb
|
151
|
+
- test/pubrec_packet_test.rb
|
152
|
+
- test/packet_handler_test.rb
|