paho-mqtt 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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