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,180 @@
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 Publisher
17
+
18
+ def initialize(sender)
19
+ @waiting_puback = []
20
+ @waiting_pubrec = []
21
+ @waiting_pubrel = []
22
+ @waiting_pubcomp = []
23
+ @puback_mutex = Mutex.new
24
+ @pubrec_mutex = Mutex.new
25
+ @pubrel_mutex = Mutex.new
26
+ @pubcomp_mutex = Mutex.new
27
+ @sender = sender
28
+ end
29
+
30
+ def sender=(sender)
31
+ @sender = sender
32
+ end
33
+
34
+ def send_publish(topic, payload, retain, qos, new_id)
35
+ packet = PahoMqtt::Packet::Publish.new(
36
+ :id => new_id,
37
+ :topic => topic,
38
+ :payload => payload,
39
+ :retain => retain,
40
+ :qos => qos
41
+ )
42
+ @sender.append_to_writing(packet)
43
+ case qos
44
+ when 1
45
+ @puback_mutex.synchronize{
46
+ @waiting_puback.push({:id => new_id, :packet => packet, :timestamp => Time.now})
47
+ }
48
+ when 2
49
+ @pubrec_mutex.synchronize{
50
+ @waiting_pubrec.push({:id => new_id, :packet => packet, :timestamp => Time.now})
51
+ }
52
+ end
53
+ MQTT_ERR_SUCCESS
54
+ end
55
+
56
+ def do_publish(qos, packet_id)
57
+ case qos
58
+ when 0
59
+ when 1
60
+ send_puback(packet_id)
61
+ when 2
62
+ send_pubrec(packet_id)
63
+ else
64
+ @logger.error("The packet qos value is invalid in publish.") if logger?
65
+ raise PacketException
66
+ end
67
+ MQTT_ERR_SUCCESS
68
+ end
69
+
70
+ def send_puback(packet_id)
71
+ packet = PahoMqtt::Packet::Puback.new(
72
+ :id => packet_id
73
+ )
74
+ @sender.append_to_writing(packet)
75
+ MQTT_ERR_SUCCESS
76
+ end
77
+
78
+ def do_puback(packet_id)
79
+ @puback_mutex.synchronize{
80
+ @waiting_puback.delete_if { |pck| pck[:id] == packet_id }
81
+ }
82
+ MQTT_ERR_SUCCESS
83
+ end
84
+
85
+ def send_pubrec(packet_id)
86
+ packet = PahoMqtt::Packet::Pubrec.new(
87
+ :id => packet_id
88
+ )
89
+ @sender.append_to_writing(packet)
90
+ @pubrel_mutex.synchronize{
91
+ @waiting_pubrel.push({:id => packet_id , :packet => packet, :timestamp => Time.now})
92
+ }
93
+ MQTT_ERR_SUCCESS
94
+ end
95
+
96
+ def do_pubrec(packet_id)
97
+ @pubrec_mutex.synchronize {
98
+ @waiting_pubrec.delete_if { |pck| pck[:id] == packet_id }
99
+ }
100
+ send_pubrel(packet_id)
101
+ MQTT_ERR_SUCCESS
102
+ end
103
+
104
+ def send_pubrel(packet_id)
105
+ packet = PahoMqtt::Packet::Pubrel.new(
106
+ :id => packet_id
107
+ )
108
+ @sender.append_to_writing(packet)
109
+ @pubcomp_mutex.synchronize{
110
+ @waiting_pubcomp.push({:id => packet_id, :packet => packet, :timestamp => Time.now})
111
+ }
112
+ MQTT_ERR_SUCCESS
113
+ end
114
+
115
+ def do_pubrel(packet_id)
116
+ @pubrel_mutex.synchronize {
117
+ @waiting_pubrel.delete_if { |pck| pck[:id] == packet_id }
118
+ }
119
+ send_pubcomp(packet_id)
120
+ MQTT_ERR_SUCCESS
121
+ end
122
+
123
+ def send_pubcomp(packet_id)
124
+ packet = PahoMqtt::Packet::Pubcomp.new(
125
+ :id => packet_id
126
+ )
127
+ @sender.append_to_writing(packet)
128
+ MQTT_ERR_SUCCESS
129
+ end
130
+
131
+ def do_pubcomp(packet_id)
132
+ @pubcomp_mutex.synchronize {
133
+ @waiting_pubcomp.delete_if { |pck| pck[:id] == packet_id }
134
+ }
135
+ MQTT_ERR_SUCCESS
136
+ end
137
+
138
+ def config_all_message_queue
139
+ config_message_queue(@waiting_puback, @puback_mutex, MAX_PUBACK)
140
+ config_message_queue(@waiting_pubrec, @pubrec_mutex, MAX_PUBREC)
141
+ config_message_queue(@waiting_pubrel, @pubrel_mutex, MAX_PUBREL)
142
+ config_message_queue(@waiting_pubcomp, @pubcomp_mutex, MAX_PUBCOMP)
143
+ end
144
+
145
+ def config_message_queue(queue, mutex, max_packet)
146
+ mutex.synchronize {
147
+ cnt = 0
148
+ queue.each do |pck|
149
+ pck[:packet].dup ||= true
150
+ if cnt <= max_packet
151
+ @sender.append_to_writing(pck[:packet])
152
+ cnt += 1
153
+ end
154
+ end
155
+ }
156
+ end
157
+
158
+ def check_waiting_publisher
159
+ @sender.check_ack_alive(@waiting_puback, @puback_mutex, MAX_PUBACK)
160
+ @sender.check_ack_alive(@waiting_pubrec, @pubrec_mutex, MAX_PUBREC)
161
+ @sender.check_ack_alive(@waiting_pubrel, @pubrel_mutex, MAX_PUBREL)
162
+ @sender.check_ack_alive(@waiting_pubcomp, @pubcomp_mutex, MAX_PUBCOMP)
163
+ end
164
+
165
+ def flush_publisher
166
+ @puback_mutex.synchronize {
167
+ @waiting_puback = []
168
+ }
169
+ @pubrec_mutex.synchronize {
170
+ @waiting_pubrec = []
171
+ }
172
+ @pubrel_mutex.synchronize {
173
+ @waiting_pubrel = []
174
+ }
175
+ @pubcomp_mutex.synchronize {
176
+ @waiting_pubcomp = []
177
+ }
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,90 @@
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 Sender
17
+
18
+ attr_accessor :last_ping_req
19
+
20
+ def initialize(ack_timeout)
21
+ @socket = nil
22
+ @writing_queue = []
23
+ @writing_mutex = Mutex.new
24
+ @last_ping_req = -1
25
+ @ack_timeout = ack_timeout
26
+ end
27
+
28
+ def socket=(socket)
29
+ @socket = socket
30
+ end
31
+
32
+ def send_packet(packet)
33
+ begin
34
+ @socket.write(packet.to_s) unless @socket.nil? || @socket.closed?
35
+ @last_ping_req = Time.now
36
+ MQTT_ERR_SUCCESS
37
+ rescue StandardError
38
+ raise WritingException
39
+ end
40
+ end
41
+
42
+ def append_to_writing(packet)
43
+ @writing_mutex.synchronize {
44
+ @writing_queue.push(packet)
45
+ }
46
+ MQTT_ERR_SUCCESS
47
+ end
48
+
49
+ @writing_mutex.synchronize {
50
+ def writing_loop(max_packet)
51
+ cnt = 0
52
+ while !@writing_queue.empty? && cnt < max_packet do
53
+ packet = @writing_queue.shift
54
+ send_packet(packet)
55
+ cnt += 1
56
+ end
57
+ }
58
+ MQTT_ERR_SUCCESS
59
+ end
60
+
61
+ def flush_waiting_packet(sending=true)
62
+ if sending
63
+ @writing_mutex.synchronize {
64
+ @writing_queue.each do |m|
65
+ send_packet(m)
66
+ end
67
+ }
68
+ else
69
+ @writing_queue = []
70
+ end
71
+ end
72
+
73
+ def check_ack_alive(queue, mutex, max_packet)
74
+ mutex.synchronize {
75
+ now = Time.now
76
+ cnt = 0
77
+ queue.each do |pck|
78
+ if now >= pck[:timestamp] + @ack_timeout
79
+ pck[:packet].dup ||= true unless pck[:packet].class == PahoMqtt::Packet::Subscribe || pck[:packet].class == PahoMqtt::Packet::Unsubscribe
80
+ unless cnt > max_packet
81
+ append_to_writing(pck[:packet])
82
+ pck[:timestamp] = now
83
+ cnt += 1
84
+ end
85
+ end
86
+ end
87
+ }
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,42 @@
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 'openssl'
16
+
17
+ module PahoMqtt
18
+ module SSLHelper
19
+ extend self
20
+
21
+ def config_ssl_context(cert_path, key_path, ca_path=nil)
22
+ ssl_context = OpenSSL::SSL::SSLContext.new
23
+ set_cert(cert_path, ssl_context)
24
+ set_key(key_path, ssl_context)
25
+ set_root_ca(ca_path, ssl_context)
26
+ ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER unless ca_path.nil?
27
+ ssl_context
28
+ end
29
+
30
+ def set_cert(cert_path, ssl_context)
31
+ ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
32
+ end
33
+
34
+ def set_key(key_path, ssl_context)
35
+ ssl_context.key = OpenSSL::PKey::RSA.new(File.read(key_path))
36
+ end
37
+
38
+ def set_root_ca(ca_path, ssl_context)
39
+ ssl_context.ca_file = ca_path
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,195 @@
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 Subscriber
17
+
18
+ attr_reader :subscribed_topics
19
+
20
+ def initialize(sender)
21
+ @waiting_suback = []
22
+ @waiting_unsuback = []
23
+ @subscribed_mutex = Mutex.new
24
+ @subscribed_topics = []
25
+ @suback_mutex = Mutex.new
26
+ @unsuback_mutex = Mutex.new
27
+ @sender = sender
28
+ end
29
+
30
+ def sender=(sender)
31
+ @sender = sender
32
+ end
33
+
34
+ def config_subscription(new_id)
35
+ unless @subscribed_topics == [] || @subscribed_topics.nil?
36
+ packet = PahoMqtt::Packet::Subscribe.new(
37
+ :id => new_id,
38
+ :topics => @subscribed_topics
39
+ )
40
+ @subscribed_mutex.synchronize {
41
+ @subscribed_topics = []
42
+ }
43
+ @suback_mutex.synchronize {
44
+ @waiting_suback.push({ :id => new_id, :packet => packet, :timestamp => Time.now })
45
+ }
46
+ @sender.send_packet(packet)
47
+ end
48
+ MQTT_ERR_SUCCESS
49
+ end
50
+
51
+ def add_subscription(max_qos, packet_id, adjust_qos)
52
+ @suback_mutex.synchronize {
53
+ adjust_qos, @waiting_suback = @waiting_suback.partition { |pck| pck[:id] == packet_id }
54
+ }
55
+ if adjust_qos.length == 1
56
+ adjust_qos = adjust_qos.first[:packet].topics
57
+ adjust_qos.each do |t|
58
+ if [0, 1, 2].include?(max_qos[0])
59
+ t[1] = max_qos.shift
60
+ elsif max_qos[0] == 128
61
+ adjust_qos.delete(t)
62
+ else
63
+
64
+ @logger.error("The qos value is invalid in subscribe.") if logger?
65
+ raise PacketException
66
+ end
67
+ end
68
+ else
69
+ @logger.error("The packet id is invalid, already used.") if logger?
70
+ raise PacketException
71
+ end
72
+ @subscribed_mutex.synchronize {
73
+ @subscribed_topics.concat(adjust_qos)
74
+ }
75
+ MQTT_ERR_SUCCESS
76
+ end
77
+
78
+ def remove_subscription(packet_id, to_unsub)
79
+ @unsuback_mutex.synchronize {
80
+ to_unsub, @waiting_unsuback = @waiting_unsuback.partition { |pck| pck[:id] == packet_id }
81
+ }
82
+
83
+ if to_unsub.length == 1
84
+ to_unsub = to_unsub.first[:packet].topics
85
+ else
86
+ @logger.error("The packet id is invalid, already used.") if logger?
87
+ raise PacketException
88
+ end
89
+
90
+ @subscribed_mutex.synchronize {
91
+ to_unsub.each do |filter|
92
+ @subscribed_topics.delete_if { |topic| match_filter(topic.first, filter.first) }
93
+ end
94
+ }
95
+ MQTT_ERR_SUCCESS
96
+ end
97
+
98
+ def send_subscribe(topics, new_id)
99
+ unless valid_topics?(topics) == MQTT_ERR_FAIL
100
+ packet = PahoMqtt::Packet::Subscribe.new(
101
+ :id => new_id,
102
+ :topics => topics
103
+ )
104
+ @sender.append_to_writing(packet)
105
+ @suback_mutex.synchronize {
106
+ @waiting_suback.push({ :id => new_id, :packet => packet, :timestamp => Time.now })
107
+ }
108
+ MQTT_ERR_SUCCESS
109
+ else
110
+ raise ProtocolViolation
111
+ end
112
+ end
113
+
114
+ def send_unsubscribe(topics, new_id)
115
+ unless valid_topics?(topics) == MQTT_ERR_FAIL
116
+ packet = PahoMqtt::Packet::Unsubscribe.new(
117
+ :id => new_id,
118
+ :topics => topics
119
+ )
120
+
121
+ @sender.append_to_writing(packet)
122
+ @unsuback_mutex.synchronize {
123
+ @waiting_unsuback.push({:id => new_id, :packet => packet, :timestamp => Time.now})
124
+ }
125
+ MQTT_ERR_SUCCESS
126
+ else
127
+ raise ProtocolViolation
128
+ end
129
+ end
130
+
131
+ def check_waiting_subscriber
132
+ @sender.check_ack_alive(@waiting_suback, @suback_mutex, @waiting_suback.length)
133
+ @sender.check_ack_alive(@waiting_unsuback, @unsuback_mutex, @waiting_unsuback.length)
134
+ end
135
+
136
+ def valid_topics?(topics)
137
+ unless topics.length == 0
138
+ topics.map do |topic|
139
+ return MQTT_ERR_FAIL if topic.first == ""
140
+ end
141
+ else
142
+ MQTT_ERR_FAIL
143
+ end
144
+ MQTT_ERR_SUCCESS
145
+ end
146
+
147
+
148
+ private
149
+
150
+ def match_filter(topics, filters)
151
+ check_topics(topics, filters)
152
+ index = 0
153
+ rc = false
154
+ topic = topics.split('/')
155
+ filter = filters.split('/')
156
+ while index < [topic.length, filter.length].max do
157
+ if is_end?(topic[index], filter[index])
158
+ break
159
+ elsif is_wildcard?(filter[index])
160
+ rc = index == (filter.length - 1)
161
+ break
162
+ elsif keep_running?(filter[index], topic[index])
163
+ index = index + 1
164
+ else
165
+ break
166
+ end
167
+ end
168
+ is_matching?(rc, topic.length, filter.length, index)
169
+ end
170
+
171
+ def keep_running?(filter_part, topic_part)
172
+ filter_part == topic_part || filter_part == '+'
173
+ end
174
+
175
+ def is_wildcard?(filter_part)
176
+ filter_part == '#'
177
+ end
178
+
179
+ def is_end?(topic_part, filter_part)
180
+ topic_part.nil? || filter_part.nil?
181
+ end
182
+
183
+ def is_matching?(rc, topic_length, filter_length, index)
184
+ rc || index == [topic_length, filter_length].max
185
+ end
186
+
187
+ def check_topics(topics, filters)
188
+ if topics.is_a?(String) && filters.is_a?(String)
189
+ else
190
+ @logger.error("Topics or Wildcards are not found as String.") if logger?
191
+ raise ArgumentError
192
+ end
193
+ end
194
+ end
195
+ end