paho-mqtt 1.0.0 → 1.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.
@@ -0,0 +1,167 @@
1
+ # Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
2
+ #
3
+ # All rights reserved. This program and the accompanying materials
4
+ # are made available under the terms of the Eclipse Public License v1.0
5
+ # and Eclipse Distribution License v1.0 which accompany this distribution.
6
+ #
7
+ # The Eclipse Public License is available at
8
+ # https://eclipse.org/org/documents/epl-v10.php.
9
+ # and the Eclipse Distribution License is available at
10
+ # https://eclipse.org/org/documents/edl-v10.php.
11
+ #
12
+ # Contributors:
13
+ # Pierre Goudet - initial committer
14
+
15
+ require 'socket'
16
+
17
+ module PahoMqtt
18
+ class ConnectionHelper
19
+
20
+ attr_accessor :sender
21
+
22
+ def initialize(host, port, ssl, ssl_context, ack_timeout)
23
+ @cs = MQTT_CS_DISCONNECT
24
+ @socket = nil
25
+ @host = host
26
+ @port = port
27
+ @ssl = ssl
28
+ @ssl_context = ssl_context
29
+ @ack_timeout = ack_timeout
30
+ @sender = Sender.new(ack_timeout)
31
+ end
32
+
33
+ def handler=(handler)
34
+ @handler = handler
35
+ end
36
+
37
+ def do_connect(reconnection=false)
38
+ @handler.socket = @socket
39
+ # Waiting a Connack packet for "ack_timeout" second from the remote
40
+ connect_timeout = Time.now + @ack_timeout
41
+ while (Time.now <= connect_timeout) && (!is_connected?) do
42
+ @cs = @handler.receive_packet
43
+ sleep 0.0001
44
+ end
45
+ unless is_connected?
46
+ PahoMqtt.logger.warn("Connection failed. Couldn't recieve a Connack packet from: #{@host}, socket is \"#{@socket}\".") if PahoMqtt.logger?
47
+ raise Exception.new("Connection failed. Check log for more details.") unless reconnection
48
+ end
49
+ @cs
50
+ end
51
+
52
+ def is_connected?
53
+ @cs == MQTT_CS_CONNECTED
54
+ end
55
+
56
+ def do_disconnect(publisher, explicit, mqtt_thread)
57
+ PahoMqtt.logger.debug("Disconnecting from #{@host}") if PahoMqtt.logger?
58
+ if explicit
59
+ explicit_disconnect(publisher, mqtt_thread)
60
+ end
61
+ @socket.close unless @socket.nil? || @socket.closed?
62
+ @socket = nil
63
+ end
64
+
65
+ def explicit_disconnect(publisher, mqtt_thread)
66
+ @sender.flush_waiting_packet
67
+ send_disconnect
68
+ mqtt_thread.kill if mqtt_thread && mqtt_thread.alive?
69
+ publisher.flush_publisher unless publisher.nil?
70
+ end
71
+
72
+ def setup_connection
73
+ clean_start(@host, @port)
74
+ config_socket
75
+ unless @socket.nil?
76
+ @sender.socket = @socket
77
+ end
78
+ end
79
+
80
+ def config_socket
81
+ PahoMqtt.logger.debug("Atempt to connect to host: #{@host}") if PahoMqtt.logger?
82
+ begin
83
+ tcp_socket = TCPSocket.new(@host, @port)
84
+ rescue StandardError
85
+ PahoMqtt.logger.warn("Could not open a socket with #{@host} on port #{@port}") if PahoMqtt.logger?
86
+ end
87
+ if @ssl
88
+ encrypted_socket(tcp_socket, @ssl_context)
89
+ else
90
+ @socket = tcp_socket
91
+ end
92
+ end
93
+
94
+ def encrypted_socket(tcp_socket, ssl_context)
95
+ unless ssl_context.nil?
96
+ @socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
97
+ @socket.sync_close = true
98
+ @socket.connect
99
+ else
100
+ PahoMqtt.logger.error("The ssl context was found as nil while the socket's opening.") if PahoMqtt.logger?
101
+ raise Exception
102
+ end
103
+ end
104
+
105
+ def clean_start(host, port)
106
+ self.host = host
107
+ self.port = port
108
+ unless @socket.nil?
109
+ @socket.close unless @socket.closed?
110
+ @socket = nil
111
+ end
112
+ end
113
+
114
+ def host=(host)
115
+ if host.nil? || host == ""
116
+ PahoMqtt.logger.error("The host was found as nil while the connection setup.") if PahoMqtt.logger?
117
+ raise ArgumentError
118
+ else
119
+ @host = host
120
+ end
121
+ end
122
+
123
+ def port=(port)
124
+ if port.to_i <= 0
125
+ PahoMqtt.logger.error("The port value is invalid (<= 0). Could not setup the connection.") if PahoMqtt.logger?
126
+ raise ArgumentError
127
+ else
128
+ @port = port
129
+ end
130
+ end
131
+
132
+ def send_connect(session_params)
133
+ setup_connection
134
+ packet = PahoMqtt::Packet::Connect.new(session_params)
135
+ @handler.clean_session = session_params[:clean_session]
136
+ @sender.send_packet(packet)
137
+ MQTT_ERR_SUCCESS
138
+ end
139
+
140
+ def send_disconnect
141
+ packet = PahoMqtt::Packet::Disconnect.new
142
+ @sender.send_packet(packet)
143
+ MQTT_ERR_SUCCESS
144
+ end
145
+
146
+ def send_pingreq
147
+ packet = PahoMqtt::Packet::Pingreq.new
148
+ @sender.send_packet(packet)
149
+ MQTT_ERR_SUCCESS
150
+ end
151
+
152
+ def check_keep_alive(persistent, last_ping_resp, keep_alive)
153
+ now = Time.now
154
+ timeout_req = (@sender.last_ping_req + (keep_alive * 0.7).ceil)
155
+ if timeout_req <= now && persistent
156
+ PahoMqtt.logger.debug("Checking if server is still alive.") if PahoMqtt.logger?
157
+ send_pingreq
158
+ end
159
+ timeout_resp = last_ping_resp + (keep_alive * 1.1).ceil
160
+ if timeout_resp <= now
161
+ PahoMqtt.logger.debug("No activity period over timeout, disconnecting from #{@host}") if PahoMqtt.logger?
162
+ @cs = MQTT_CS_DISCONNECT
163
+ end
164
+ @cs
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,271 @@
1
+ # Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
2
+ #
3
+ # All rights reserved. This program and the accompanying materials
4
+ # are made available under the terms of the Eclipse Public License v1.0
5
+ # and Eclipse Distribution License v1.0 which accompany this distribution.
6
+ #
7
+ # The Eclipse Public License is available at
8
+ # https://eclipse.org/org/documents/epl-v10.php.
9
+ # and the Eclipse Distribution License is available at
10
+ # https://eclipse.org/org/documents/edl-v10.php.
11
+ #
12
+ # Contributors:
13
+ # Pierre Goudet - initial committer
14
+
15
+ module PahoMqtt
16
+ class Handler
17
+
18
+ attr_reader :registered_callback
19
+ attr_accessor :last_ping_resp
20
+ attr_accessor :clean_session
21
+
22
+ def initialize
23
+ @registered_callback = []
24
+ @last_ping_resp = -1
25
+ @publisher = nil
26
+ @subscriber = nil
27
+ end
28
+
29
+ def config_pubsub(publisher, subscriber)
30
+ @publisher = publisher
31
+ @subscriber = subscriber
32
+ end
33
+
34
+ def socket=(socket)
35
+ @socket = socket
36
+ end
37
+
38
+ def receive_packet
39
+ result = IO.select([@socket], [], [], SELECT_TIMEOUT) unless @socket.nil? || @socket.closed?
40
+ unless result.nil?
41
+ packet = PahoMqtt::Packet::Base.read(@socket)
42
+ unless packet.nil?
43
+ if packet.is_a?(PahoMqtt::Packet::Connack)
44
+ @last_ping_resp = Time.now
45
+ handle_connack(packet)
46
+ else
47
+ handle_packet(packet)
48
+ @last_ping_resp = Time.now
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def handle_packet(packet)
55
+ PahoMqtt.logger.info("New packet #{packet.class} recieved.") if PahoMqtt.logger?
56
+ type = packet_type(packet)
57
+ self.send("handle_#{type}", packet)
58
+ end
59
+
60
+ def register_topic_callback(topic, callback, &block)
61
+ if topic.nil?
62
+ PahoMqtt.logger.error("The topics where the callback is trying to be registered have been found nil.") if PahoMqtt.logger?
63
+ raise ArgumentError
64
+ end
65
+ clear_topic_callback(topic)
66
+ if block_given?
67
+ @registered_callback.push([topic, block])
68
+ elsif !(callback.nil?) && callback.is_a?(Proc)
69
+ @registered_callback.push([topic, callback])
70
+ end
71
+ MQTT_ERR_SUCCESS
72
+ end
73
+
74
+ def clear_topic_callback(topic)
75
+ if topic.nil?
76
+ PahoMqtt.logger.error("The topics where the callback is trying to be unregistered have been found nil.") if PahoMqtt.logger?
77
+ raise ArgumentError
78
+ end
79
+ @registered_callback.delete_if {|pair| pair.first == topic}
80
+ MQTT_ERR_SUCCESS
81
+ end
82
+
83
+ def handle_connack(packet)
84
+ if packet.return_code == 0x00
85
+ PahoMqtt.logger.debug("Connack receive and connection accepted.") if PahoMqtt.logger?
86
+ handle_connack_accepted(packet.session_present)
87
+ else
88
+ handle_connack_error(packet.return_code)
89
+ end
90
+ @on_connack.call(packet) unless @on_connack.nil?
91
+ MQTT_CS_CONNECTED
92
+ end
93
+
94
+ def handle_connack_accepted(session_flag)
95
+ clean_session?(session_flag)
96
+ new_session?(session_flag)
97
+ old_session?(session_flag)
98
+ end
99
+
100
+ def new_session?(session_flag)
101
+ if !@clean_session && !session_flag
102
+ PahoMqtt.logger.debug("New session created for the client") if PahoMqtt.logger?
103
+ end
104
+ end
105
+
106
+ def clean_session?(session_flag)
107
+ if @clean_session && !session_flag
108
+ PahoMqtt.logger.debug("No previous session found by server, starting a new one.") if PahoMqtt.logger?
109
+ end
110
+ end
111
+
112
+ def old_session?(session_flag)
113
+ if !@clean_session && session_flag
114
+ PahoMqtt.logger.debug("Previous session restored by the server.") if PahoMqtt.logger?
115
+ end
116
+ end
117
+
118
+ def handle_pingresp(_packet)
119
+ @last_ping_resp = Time.now
120
+ end
121
+
122
+ def handle_suback(packet)
123
+ max_qos = packet.return_codes
124
+ id = packet.id
125
+ topics = []
126
+ if @subscriber.add_subscription(max_qos, id, topics) == MQTT_ERR_SUCCESS
127
+ @on_suback.call(topics) unless @on_suback.nil?
128
+ end
129
+ end
130
+
131
+ def handle_unsuback(packet)
132
+ id = packet.id
133
+ topics = []
134
+ if @subscriber.remove_subscription(id, topics) == MQTT_ERR_SUCCESS
135
+ @on_unsuback.call(topics) unless @on_unsuback.nil?
136
+ end
137
+ end
138
+
139
+ def handle_publish(packet)
140
+ id = packet.id
141
+ qos = packet.qos
142
+ if @publisher.do_publish(qos, id) == MQTT_ERR_SUCCESS
143
+ @on_message.call(packet) unless @on_message.nil?
144
+ @registered_callback.assoc(packet.topic).last.call(packet) if @registered_callback.any? { |pair| pair.first == packet.topic}
145
+ end
146
+ end
147
+
148
+ def handle_puback(packet)
149
+ id = packet.id
150
+ if @publisher.do_puback(id) == MQTT_ERR_SUCCESS
151
+ @on_puback.call(packet) unless @on_puback.nil?
152
+ end
153
+ end
154
+
155
+ def handle_pubrec(packet)
156
+ id = packet.id
157
+ if @publisher.do_pubrec(id) == MQTT_ERR_SUCCESS
158
+ @on_pubrec.call(packet) unless @on_pubrec.nil?
159
+ end
160
+ end
161
+
162
+ def handle_pubrel(packet)
163
+ id = packet.id
164
+ if @publisher.do_pubrel(id) == MQTT_ERR_SUCCESS
165
+ @on_pubrel.call(packet) unless @on_pubrel.nil?
166
+ end
167
+ end
168
+
169
+ def handle_pubcomp(packet)
170
+ id = packet.id
171
+ if @publisher.do_pubcomp(id) == MQTT_ERR_SUCCESS
172
+ @on_pubcomp.call(packet) unless @on_pubcomp.nil?
173
+ end
174
+ end
175
+
176
+ def handle_connack_error(return_code)
177
+ if return_code == 0x01
178
+ raise LowVersionException
179
+ elsif CONNACK_ERROR_MESSAGE.has_key(return_code.to_sym)
180
+ PahoMqtt.logger.warm(CONNACK_ERRO_MESSAGE[return_code])
181
+ MQTT_CS_DISCONNECTED
182
+ else
183
+ PahoMqtt.logger("Unknown return code for CONNACK packet: #{return_code}")
184
+ raise PacketException
185
+ end
186
+ end
187
+
188
+ def on_connack(&block)
189
+ @on_connack = block if block_given?
190
+ @on_connack
191
+ end
192
+
193
+ def on_suback(&block)
194
+ @on_suback = block if block_given?
195
+ @on_suback
196
+ end
197
+
198
+ def on_unsuback(&block)
199
+ @on_unsuback = block if block_given?
200
+ @on_unsuback
201
+ end
202
+
203
+ def on_puback(&block)
204
+ @on_puback = block if block_given?
205
+ @on_puback
206
+ end
207
+
208
+ def on_pubrec(&block)
209
+ @on_pubrec = block if block_given?
210
+ @on_pubrec
211
+ end
212
+
213
+ def on_pubrel(&block)
214
+ @on_pubrel = block if block_given?
215
+ @on_pubrel
216
+ end
217
+
218
+ def on_pubcomp(&block)
219
+ @on_pubcomp = block if block_given?
220
+ @on_pubcomp
221
+ end
222
+
223
+ def on_message(&block)
224
+ @on_message = block if block_given?
225
+ @on_message
226
+ end
227
+
228
+ def on_connack=(callback)
229
+ @on_connack = callback if callback.is_a?(Proc)
230
+ end
231
+
232
+ def on_suback=(callback)
233
+ @on_suback = callback if callback.is_a?(Proc)
234
+ end
235
+
236
+ def on_unsuback=(callback)
237
+ @on_unsuback = callback if callback.is_a?(Proc)
238
+ end
239
+
240
+ def on_puback=(callback)
241
+ @on_puback = callback if callback.is_a?(Proc)
242
+ end
243
+
244
+ def on_pubrec=(callback)
245
+ @on_pubrec = callback if callback.is_a?(Proc)
246
+ end
247
+
248
+ def on_pubrel=(callback)
249
+ @on_pubrel = callback if callback.is_a?(Proc)
250
+ end
251
+
252
+ def on_pubcomp=(callback)
253
+ @on_pubcomp = callback if callback.is_a?(Proc)
254
+ end
255
+
256
+ def on_message=(callback)
257
+ @on_message = callback if callback.is_a?(Proc)
258
+ end
259
+
260
+ def packet_type(packet)
261
+ type = packet.class
262
+ if PahoMqtt::PACKET_TYPES[3..13].include?(type)
263
+ type.to_s.split('::').last.downcase
264
+ else
265
+ puts "Packet: #{packet.inspect}"
266
+ PahoMqtt.logger.error("Received an unexpeceted packet: #{packet}") if PahoMqtt.logger?
267
+ raise PacketException
268
+ end
269
+ end
270
+ end
271
+ end
@@ -1,4 +1,18 @@
1
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
+
2
16
  require "paho_mqtt/packet/base"
3
17
  require "paho_mqtt/packet/connect"
4
18
  require "paho_mqtt/packet/connack"