mosq 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e3e826b96ac902207ad690a3ea90a49acc266eb0
4
+ data.tar.gz: b0208ffc9b595687d936b704c15ce3dd0b06b864
5
+ SHA512:
6
+ metadata.gz: 7b605dbc96e3bd96b3c75fe2d9ed1f20c8ff187c2e752e1f2ae77ea5971bb1621100ade4a59f1f8fe82a74edff215d5779be7449ed417c91fb170cf79a1eef27
7
+ data.tar.gz: 00d94526260cb7ba5313125ddbfa3aa5e742db6853f072fe52b23b03297885ba334d6f8443c12d8768f6c975280c36028c80bf7cf5b969a315035b162cf49318
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Joe McIlvain
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.
@@ -0,0 +1,9 @@
1
+ # mosq
2
+
3
+ [![Build Status](https://circleci.com/gh/jemc/ruby-mosq/tree/master.svg?style=svg)](https://circleci.com/gh/jemc/ruby-mosq/tree/master) 
4
+ [![Gem Version](https://badge.fury.io/rb/mosq.png)](http://badge.fury.io/rb/mosq) 
5
+ [![Join the chat at https://gitter.im/jemc/ruby-mosq](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jemc/ruby-mosq?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
6
+
7
+ [MQTT](http://mqtt.org/) client library based on [FFI](https://github.com/ffi/ffi/wiki) bindings for [libmosquitto](http://mosquitto.org/man/libmosquitto-3.html).
8
+
9
+ ##### `$ gem install mosq`
@@ -0,0 +1,47 @@
1
+
2
+ require 'rake/clean'
3
+ require 'ffi'
4
+
5
+ FILES = {}
6
+
7
+ task :default => [:build, :compact]
8
+
9
+ def self.file_task(filename, opts, &block)
10
+ name, dep = opts.is_a?(Hash) ? opts.to_a.first : [opts, nil]
11
+
12
+ FILES[name] = filename
13
+ CLEAN.include filename
14
+ task name => filename
15
+
16
+ if dep
17
+ file filename => FILES[dep], &block
18
+ else
19
+ file filename, &block
20
+ end
21
+ end
22
+
23
+ def cmd(string)
24
+ fail "Command failed: #{string}" unless system(string)
25
+ end
26
+
27
+ file_task 'mosquitto.tar.gz', :download_tarball do
28
+ version = "1.4.2"
29
+ release = "http://mosquitto.org/files/source/mosquitto-#{version}.tar.gz"
30
+ cmd "wget -O #{FILES[:download_tarball]} #{release}"
31
+ end
32
+
33
+ file_task 'mosquitto', :download => :download_tarball do
34
+ cmd "tar -zxf #{FILES[:download_tarball]}"
35
+ cmd "mv mosquitto-* #{FILES[:download]}"
36
+ end
37
+
38
+ file_task "libmosquitto.#{::FFI::Platform::LIBSUFFIX}", :build => :download do
39
+ cmd "/usr/bin/env sh -c 'cd #{FILES[:download]} && env CFLAGS=-g make'"
40
+ cmd "cp #{FILES[:download]}/lib/#{FILES[:build]}* ./#{FILES[:build]}"
41
+ end
42
+
43
+ task :compact => FILES[:build] do
44
+ FILES.each do |key, filename|
45
+ cmd "rm -rf #{filename}" unless key == :build
46
+ end
47
+ end
@@ -0,0 +1,13 @@
1
+
2
+ require_relative 'mosq/util'
3
+ require_relative 'mosq/ffi'
4
+ require_relative 'mosq/ffi/error'
5
+
6
+ require_relative 'mosq/client'
7
+
8
+ # Call to initialize the library
9
+ Mosq::Util.error_check "initializing the libmosquitto library",
10
+ Mosq::FFI.mosquitto_lib_init
11
+
12
+ # Call cleanup at exit clean up the library
13
+ at_exit { Mosq::FFI.mosquitto_lib_cleanup }
@@ -0,0 +1,324 @@
1
+
2
+ require 'socket'
3
+
4
+ require_relative 'client/bucket'
5
+
6
+
7
+ module Mosq
8
+ class Client
9
+
10
+ # Raised when an operation is performed on an already-destroyed {Client}.
11
+ class DestroyedError < RuntimeError; end
12
+
13
+ # Create a new {Client} instance with the given properties.
14
+ def initialize(*args)
15
+ @options = Util.connection_info(*args)
16
+
17
+ @options[:heartbeat] ||= 30 # seconds
18
+ @protocol_timeout = DEFAULT_PROTOCOL_TIMEOUT
19
+
20
+ Util.null_check "creating the client",
21
+ (@ptr = FFI.mosquitto_new(@options[:client_id], true, nil))
22
+
23
+ @bucket = Bucket.new(@ptr)
24
+ @event_handlers = {}
25
+
26
+ @packet_id_ptr = Util.mem_ptr(:int)
27
+
28
+ @finalizer = self.class.create_finalizer_for(@ptr)
29
+ ObjectSpace.define_finalizer(self, @finalizer)
30
+ end
31
+
32
+ # @api private
33
+ def self.create_finalizer_for(ptr)
34
+ Proc.new do
35
+ FFI.mosquitto_destroy(ptr)
36
+ end
37
+ end
38
+
39
+ def username; @options.fetch(:username); end
40
+ def password; @options.fetch(:password); end
41
+ def host; @options.fetch(:host); end
42
+ def port; @options.fetch(:port); end
43
+ def ssl?; @options.fetch(:ssl); end
44
+ def heartbeat; @options.fetch(:heartbeat); end
45
+
46
+ # The maximum time interval the user application should wait between
47
+ # yielding control back to the client object by calling run_loop!.
48
+ def max_poll_interval
49
+ @options.fetch(:heartbeat) / 2.0
50
+ end
51
+
52
+ def ptr
53
+ raise DestroyedError unless @ptr
54
+ @ptr
55
+ end
56
+ private :ptr
57
+
58
+ # Initiate the connection with the server.
59
+ # It is necessary to call this before any other communication.
60
+ def start
61
+ Util.error_check "configuring the username and password",
62
+ FFI.mosquitto_username_pw_set(ptr, @options[:usernam], @options[:password])
63
+
64
+ Util.error_check "connecting to #{@options[:host]}",
65
+ FFI.mosquitto_connect(ptr, @options[:host], @options[:port], @options[:heartbeat])
66
+
67
+ @ruby_socket = Socket.for_fd(FFI.mosquitto_socket(ptr))
68
+ @ruby_socket.autoclose = false
69
+
70
+ res = fetch_response(:connect, nil)
71
+ raise Mosq::FFI::Error::NoConn, res.fetch(:message) \
72
+ unless res.fetch(:status) == 0
73
+
74
+ self
75
+ end
76
+
77
+ # Gracefully close the connection with the server.
78
+ def close
79
+ @ruby_socket = nil
80
+
81
+ Util.error_check "closing the connection to #{@options[:host]}",
82
+ FFI.mosquitto_disconnect(ptr)
83
+
84
+ self
85
+ rescue Mosq::FFI::Error::NoConn
86
+ self
87
+ end
88
+
89
+ # Free the native resources associated with this object. This will
90
+ # be done automatically on garbage collection if not called explicitly.
91
+ def destroy
92
+ if @finalizer
93
+ @finalizer.call
94
+ ObjectSpace.undefine_finalizer(self)
95
+ end
96
+ @ptr = @finalizer = @ruby_socket = @bucket = nil
97
+
98
+ self
99
+ end
100
+
101
+ # Register a handler for events on the given channel of the given type.
102
+ # Only one handler for each event type may be registered at a time.
103
+ # If no callable or block is given, the handler will be cleared.
104
+ #
105
+ # @param type [Symbol] The type of event to watch for.
106
+ # @param callable [#call,nil] The callable handler if no block is given.
107
+ # @param block [Proc,nil] The handler block to register.
108
+ # @return [Proc,#call,nil] The given block or callable.
109
+ # @yieldparam event [Hash] The event passed to the handler.
110
+ #
111
+ def on_event(type, callable=nil, &block)
112
+ handler = block || callable
113
+ raise ArgumentError, "expected block or callable as the event handler" \
114
+ unless handler.respond_to?(:call)
115
+
116
+ @event_handlers[type.to_sym] = handler
117
+ handler
118
+ end
119
+ alias_method :on, :on_event
120
+
121
+ # Unregister the event handler associated with the given channel and method.
122
+ #
123
+ # @param type [Symbol] The type of protocol method to watch for.
124
+ # @return [Proc,nil] This removed handler, if any.
125
+ #
126
+ def clear_event_handler(type)
127
+ @event_handlers.delete(type.to_sym)
128
+ end
129
+
130
+ # The timeout to use when waiting for protocol events, in seconds.
131
+ # By default, this has the value of {DEFAULT_PROTOCOL_TIMEOUT}.
132
+ # When set, it affects operations like {#run_loop!}.
133
+ attr_accessor :protocol_timeout
134
+ DEFAULT_PROTOCOL_TIMEOUT = 30 # seconds
135
+
136
+ # Subscribe to the given topic. Messages with matching topic will be
137
+ # delivered to the {:message} event handler registered with {on_event}.
138
+ #
139
+ # @param topic [String] The topic patten to subscribe to.
140
+ # @param qos [Integer] The QoS level to expect for received messages.
141
+ # @return [Client] This client.
142
+ #
143
+ def subscribe(topic, qos: 0)
144
+ Util.error_check "subscribing to a topic",
145
+ FFI.mosquitto_subscribe(ptr, @packet_id_ptr, topic, qos)
146
+
147
+ fetch_response(:subscribe, @packet_id_ptr.read_int)
148
+
149
+ self
150
+ end
151
+
152
+ # Unsubscribe from the given topic.
153
+ #
154
+ # @param topic [String] The topic patten to unsubscribe from.
155
+ # @return [Client] This client.
156
+ #
157
+ def unsubscribe(topic)
158
+ Util.error_check "unsubscribing from a topic",
159
+ FFI.mosquitto_unsubscribe(ptr, @packet_id_ptr, topic)
160
+
161
+ fetch_response(:unsubscribe, @packet_id_ptr.read_int)
162
+
163
+ self
164
+ end
165
+
166
+ # Publish a message with the given topic and payload.
167
+ #
168
+ # @param topic [String] The topic to publish on.
169
+ # @param payload [String] The payload to publish.
170
+ # @param qos [Integer] The QoS level to use for the publish transaction.
171
+ # @param retain [Boolean] Whether the broker should retain the message.
172
+ # @return [Client] This client.
173
+ #
174
+ def publish(topic, payload, qos: 0, retain: false)
175
+ Util.error_check "publishing a message",
176
+ FFI.mosquitto_publish(ptr, @packet_id_ptr,
177
+ topic, payload.bytesize, payload, qos, retain)
178
+
179
+ fetch_response(:publish, @packet_id_ptr.read_int)
180
+
181
+ self
182
+ end
183
+
184
+ # Fetch and handle events in a loop that blocks the calling thread.
185
+ # The loop will continue until the {#break!} method is called from within
186
+ # an event handler, or until the given timeout duration has elapsed.
187
+ # Note that this must be called at least as frequently as the heartbeat
188
+ # interval to ensure that the client is not disconnected - if control is
189
+ # not yielded to the client transport heartbeats will not be maintained.
190
+ #
191
+ # @param timeout [Float] the maximum time to run the loop, in seconds;
192
+ # if none is given, the value is {#protocol_timeout} or until {#break!}
193
+ # @param block [Proc,nil] if given, the block will be yielded each
194
+ # non-exception event received on any channel. Other handlers or
195
+ # response fetchings that match the event will still be processed,
196
+ # as the block does not consume the event or replace the handlers.
197
+ # @return [undefined] assume no value - reserved for future use.
198
+ #
199
+ def run_loop!(timeout: protocol_timeout, &block)
200
+ timeout = Float(timeout) if timeout
201
+ fetch_events(timeout, &block)
202
+ nil
203
+ end
204
+
205
+ # Yield control to the client object to do any connection-oriented work
206
+ # that needs to be done, including heartbeating. This is the same as
207
+ # calling {#run_loop!} with no block and a timeout of 0.
208
+ #
209
+ def run_immediate!
210
+ run_loop!(timeout: 0)
211
+ end
212
+
213
+ # Stop iterating from within an execution of the {#run_loop!} method.
214
+ # Call this method only from within an event handler.
215
+ # It will take effect only after the handler finishes running.
216
+ #
217
+ # @return [nil]
218
+ #
219
+ def break!
220
+ @breaking = true
221
+ nil
222
+ end
223
+
224
+ private
225
+
226
+ # Calculate the amount of the timeout remaining from the given start time
227
+ def remaining_timeout(timeout=0, start=Time.now)
228
+ return nil unless timeout
229
+ timeout = timeout - (Time.now - start)
230
+ timeout < 0 ? 0 : timeout
231
+ end
232
+
233
+ # Block until there is readable data on the internal ruby socket,
234
+ # returning true if there is readable data, or false if time expired.
235
+ def select(timeout=0)
236
+ return false unless @ruby_socket
237
+ IO.select([@ruby_socket], [], [], timeout) ? true : false
238
+ rescue Errno::EBADF
239
+ false
240
+ end
241
+
242
+ # Execute the handler for this type of event, if any.
243
+ def handle_incoming_event(event)
244
+ if (handler = (@event_handlers[event.fetch(:type)]))
245
+ handler.call(event)
246
+ end
247
+ end
248
+
249
+ def connection_housekeeping
250
+ # Do any pending outbound writes.
251
+ while FFI.mosquitto_want_write(ptr)
252
+ Util.error_check "sending outbound packets",
253
+ FFI.mosquitto_loop_write(ptr, 1)
254
+ end
255
+
256
+ # Do any pending stateful protocol packets.
257
+ Util.error_check "handling stateful protocol packets",
258
+ FFI.mosquitto_loop_misc(ptr)
259
+ end
260
+
261
+ # Return the next incoming event as a Hash, or nil if time expired.
262
+ def fetch_next_event(timeout=0, start=Time.now)
263
+ max_timeout = max_poll_interval
264
+
265
+ # Check if any data is immediately available to read
266
+ if select(0)
267
+ Util.error_check "reading immediate inbound packets",
268
+ FFI.mosquitto_loop_read(ptr, 1)
269
+ end
270
+
271
+ while true
272
+ connection_housekeeping
273
+
274
+ # Check for an event already waiting in the bucket
275
+ return @bucket.events.shift unless @bucket.events.empty?
276
+
277
+ # Calculate remaining timeout and break if breaking or time expired.
278
+ remaining = remaining_timeout(timeout, start)
279
+ return nil if remaining && remaining <= 0
280
+
281
+ # Wait for data to arrive on the socket.
282
+ select_timeout = remaining ? [remaining, max_timeout].min : nil
283
+ if select(select_timeout)
284
+ Util.error_check "reading inbound packets",
285
+ FFI.mosquitto_loop_read(ptr, 1)
286
+
287
+ unless @bucket.events.empty?
288
+ connection_housekeeping
289
+ return @bucket.events.shift
290
+ end
291
+ end
292
+ end
293
+ end
294
+
295
+ # Internal implementation of the {#run_loop!} method.
296
+ def fetch_events(timeout=protocol_timeout, start=Time.now)
297
+ while (event = fetch_next_event(timeout, start))
298
+ handle_incoming_event(event)
299
+ yield event if block_given?
300
+ break if @breaking
301
+ end
302
+ end
303
+
304
+ # Internal implementation of synchronous responses.
305
+ def fetch_response(expected_type, expected_packet_id, timeout=protocol_timeout, start=Time.now)
306
+ unwanted_events = []
307
+
308
+ while (event = fetch_next_event(timeout, start))
309
+ if (event.fetch(:type) == expected_type) && (
310
+ !expected_packet_id ||
311
+ event.fetch(:packet_id) == expected_packet_id
312
+ )
313
+ unwanted_events.reverse_each { |e| @bucket.events.unshift(e) }
314
+ handle_incoming_event(event)
315
+ return event
316
+ else
317
+ unwanted_events.push(event)
318
+ end
319
+ end
320
+
321
+ raise FFI::Error::Timeout, "waiting for #{expected_type} response"
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,76 @@
1
+
2
+ module Mosq
3
+ class Client
4
+
5
+ # @api private
6
+ class Bucket
7
+ def initialize(ptr)
8
+ FFI.mosquitto_connect_callback_set ptr, method(:on_connect)
9
+ # FFI.mosquitto_disconnect_callback_set ptr, method(:on_disconnect)
10
+ FFI.mosquitto_publish_callback_set ptr, method(:on_publish)
11
+ FFI.mosquitto_message_callback_set ptr, method(:on_message)
12
+ FFI.mosquitto_subscribe_callback_set ptr, method(:on_subscribe)
13
+ FFI.mosquitto_unsubscribe_callback_set ptr, method(:on_unsubscribe)
14
+ # FFI.mosquitto_log_callback_set ptr, method(:on_log)
15
+
16
+ @events = []
17
+ end
18
+
19
+ attr_reader :events
20
+
21
+ def on_connect(ptr, _, status)
22
+ @events << {
23
+ type: :connect,
24
+ status: status,
25
+ message: case status
26
+ when 0; "success"
27
+ when 1; "connection refused (unacceptable protocol version)"
28
+ when 2; "connection refused (identifier rejected)"
29
+ when 3; "connection refused (broker unavailable)"
30
+ else "unknown connection failure"
31
+ end,
32
+ }
33
+ end
34
+
35
+ # def on_disconnect(ptr, _, status)
36
+
37
+ # end
38
+
39
+ def on_publish(ptr, _, packet_id)
40
+ @events << {
41
+ type: :publish,
42
+ packet_id: packet_id,
43
+ }
44
+ end
45
+
46
+ def on_message(ptr, _, message)
47
+ @events << {
48
+ type: :message,
49
+ topic: message[:topic].read_string,
50
+ payload: message[:payload].read_bytes(message[:payloadlen]),
51
+ retained: message[:retain],
52
+ qos: message[:qos],
53
+ }
54
+ end
55
+
56
+ def on_subscribe(ptr, _, packet_id, _, _)
57
+ @events << {
58
+ type: :subscribe,
59
+ packet_id: packet_id,
60
+ }
61
+ end
62
+
63
+ def on_unsubscribe(ptr, _, packet_id)
64
+ @events << {
65
+ type: :unsubscribe,
66
+ packet_id: packet_id,
67
+ }
68
+ end
69
+
70
+ # def on_log(ptr, _, status, string)
71
+
72
+ # end
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,135 @@
1
+
2
+ require 'ffi'
3
+
4
+
5
+ module Mosq
6
+
7
+ # Bindings and wrappers for the native functions and structures exposed by
8
+ # the libmosquitto C library. This module is for internal use only so that
9
+ # all dependencies on the implementation of the C library are abstracted.
10
+ # @api private
11
+ module FFI
12
+ extend ::FFI::Library
13
+
14
+ libfile = "libmosquitto.#{::FFI::Platform::LIBSUFFIX}"
15
+
16
+ ffi_lib ::FFI::Library::LIBC
17
+ ffi_lib \
18
+ File.expand_path("../../ext/mosq/#{libfile}", File.dirname(__FILE__))
19
+
20
+ opts = {
21
+ blocking: true # only necessary on MRI to deal with the GIL.
22
+ }
23
+
24
+ attach_function :free, [:pointer], :void, **opts
25
+ attach_function :malloc, [:size_t], :pointer, **opts
26
+
27
+ class Boolean
28
+ extend ::FFI::DataConverter
29
+ native_type ::FFI::TypeDefs[:int]
30
+ def self.to_native val, ctx; val ? 1 : 0; end
31
+ def self.from_native val, ctx; val != 0; end
32
+ end
33
+
34
+ class Message < ::FFI::Struct
35
+ layout :mid, :int,
36
+ :topic, :pointer,
37
+ :payload, :pointer,
38
+ :payloadlen, :int,
39
+ :qos, :int,
40
+ :retain, Boolean
41
+ end
42
+
43
+ Status = enum ::FFI::TypeDefs[:int], [
44
+ :conn_pending, -1,
45
+ :success, 0,
46
+ :nomem, 1,
47
+ :protocol, 2,
48
+ :inval, 3,
49
+ :no_conn, 4,
50
+ :conn_refused, 5,
51
+ :not_found, 6,
52
+ :conn_lost, 7,
53
+ :tls, 8,
54
+ :payload_size, 9,
55
+ :not_supported, 10,
56
+ :auth, 11,
57
+ :acl_denied, 12,
58
+ :unknown, 13,
59
+ :errno, 14,
60
+ :eai, 15,
61
+ :proxy, 16,
62
+ ]
63
+
64
+ Option = enum [
65
+ :protocol_version, 1,
66
+ ]
67
+
68
+ client = :pointer
69
+
70
+ callback :on_connect, [client, :pointer, :int], :void
71
+ callback :on_disconnect, [client, :pointer, :int], :void
72
+ callback :on_publish, [client, :pointer, :int], :void
73
+ callback :on_message, [client, :pointer, Message.ptr], :void
74
+ callback :on_subscribe, [client, :pointer, :int, :int, :pointer], :void
75
+ callback :on_unsubscribe, [client, :pointer, :int], :void
76
+ callback :on_log, [client, :pointer, :int, :string], :void
77
+
78
+ attach_function :mosquitto_lib_version, [:pointer, :pointer, :pointer], Status, **opts
79
+ attach_function :mosquitto_lib_init, [], Status, **opts
80
+ attach_function :mosquitto_lib_cleanup, [], Status, **opts
81
+ attach_function :mosquitto_new, [:string, Boolean, :pointer], client, **opts
82
+ attach_function :mosquitto_destroy, [client], :void, **opts
83
+ attach_function :mosquitto_reinitialise, [client, :string, Boolean, :pointer], Status, **opts
84
+ attach_function :mosquitto_will_set, [client, :string, :int, :pointer, :int, Boolean], Status, **opts
85
+ attach_function :mosquitto_will_clear, [client], Status, **opts
86
+ attach_function :mosquitto_username_pw_set, [client, :string, :string], Status, **opts
87
+ attach_function :mosquitto_connect, [client, :string, :int, :int], Status, **opts
88
+ attach_function :mosquitto_connect_bind, [client, :string, :int, :int, :string], Status, **opts
89
+ attach_function :mosquitto_connect_async, [client, :string, :int, :int], Status, **opts
90
+ attach_function :mosquitto_connect_bind_async, [client, :string, :int, :int, :string], Status, **opts
91
+ attach_function :mosquitto_connect_srv, [client, :string, :int, :string], Status, **opts
92
+ attach_function :mosquitto_reconnect, [client], Status, **opts
93
+ attach_function :mosquitto_reconnect_async, [client], Status, **opts
94
+ attach_function :mosquitto_disconnect, [client], Status, **opts
95
+ attach_function :mosquitto_publish, [client, :pointer, :string, :int, :pointer, :int, Boolean], Status, **opts
96
+ attach_function :mosquitto_subscribe, [client, :pointer, :string, :int], Status, **opts
97
+ attach_function :mosquitto_unsubscribe, [client, :pointer, :string], Status, **opts
98
+ attach_function :mosquitto_message_copy, [Message.ptr, Message.ptr], Status, **opts
99
+ attach_function :mosquitto_message_free, [:pointer], :void, **opts
100
+ attach_function :mosquitto_loop, [client, :int, :int], Status, **opts
101
+ attach_function :mosquitto_loop_forever, [client, :int, :int], Status, **opts
102
+ attach_function :mosquitto_loop_start, [client], Status, **opts
103
+ attach_function :mosquitto_loop_stop, [client, Boolean], Status, **opts
104
+ attach_function :mosquitto_socket, [client], :int, **opts
105
+ attach_function :mosquitto_loop_read, [client, :int], Status, **opts
106
+ attach_function :mosquitto_loop_write, [client, :int], Status, **opts
107
+ attach_function :mosquitto_loop_misc, [client], Status, **opts
108
+ attach_function :mosquitto_want_write, [client], :bool, **opts
109
+ attach_function :mosquitto_threaded_set, [client, Boolean], Status, **opts
110
+ attach_function :mosquitto_opts_set, [client, Option, :pointer], Status, **opts
111
+ attach_function :mosquitto_tls_set, [client], Status, **opts
112
+ attach_function :mosquitto_tls_insecure_set, [client, Boolean], Status, **opts
113
+ attach_function :mosquitto_tls_opts_set, [client, :int, :string, :string], Status, **opts
114
+ attach_function :mosquitto_tls_psk_set, [client, :string, :string, :string], Status, **opts
115
+ attach_function :mosquitto_connect_callback_set, [client, :on_connect], :void, **opts
116
+ attach_function :mosquitto_disconnect_callback_set, [client, :on_disconnect], :void, **opts
117
+ attach_function :mosquitto_publish_callback_set, [client, :on_publish], :void, **opts
118
+ attach_function :mosquitto_message_callback_set, [client, :on_message], :void, **opts
119
+ attach_function :mosquitto_subscribe_callback_set, [client, :on_subscribe], :void, **opts
120
+ attach_function :mosquitto_unsubscribe_callback_set, [client, :on_unsubscribe], :void, **opts
121
+ attach_function :mosquitto_log_callback_set, [client, :on_log], :void, **opts
122
+ attach_function :mosquitto_reconnect_delay_set, [client, :uint, :uint, Boolean], Status, **opts
123
+ attach_function :mosquitto_max_inflight_messages_set, [client, :uint], Status, **opts
124
+ attach_function :mosquitto_message_retry_set, [client, :uint], :void, **opts
125
+ attach_function :mosquitto_user_data_set, [client, :pointer], :void, **opts
126
+ attach_function :mosquitto_socks5_set, [client, :string, :int, :string, :string], Status, **opts
127
+ attach_function :mosquitto_strerror, [Status], :string, **opts
128
+ attach_function :mosquitto_connack_string, [:int], :string, **opts
129
+ attach_function :mosquitto_sub_topic_tokenise, [:string, :pointer, :pointer], Status, **opts
130
+ attach_function :mosquitto_sub_topic_tokens_free, [:pointer, :int], Status, **opts
131
+ attach_function :mosquitto_topic_matches_sub, [:string, :string, :pointer], Status, **opts
132
+ attach_function :mosquitto_pub_topic_check, [:string], Status, **opts
133
+ attach_function :mosquitto_sub_topic_check, [:string], Status, **opts
134
+ end
135
+ end
@@ -0,0 +1,58 @@
1
+
2
+ module Mosq
3
+ module FFI
4
+
5
+ class Error < RuntimeError
6
+ def initialize(message=nil)
7
+ @message = message
8
+ end
9
+
10
+ def message
11
+ if @message && status_message; "#{status_message} - #{@message}"
12
+ elsif @message; @message
13
+ elsif status_message; status_message
14
+ else; ""
15
+ end
16
+ end
17
+
18
+ def status_message
19
+ nil
20
+ end
21
+
22
+ def self.lookup status
23
+ if status == :errno
24
+ @errno_lookup_table.fetch(::FFI.errno)
25
+ else
26
+ @lookup_table.fetch(status)
27
+ end
28
+ end
29
+
30
+ @errno_lookup_table = {}
31
+
32
+ # Populate the errno_lookup_table
33
+ Errno.constants.each do |name|
34
+ kls = Errno.const_get(name)
35
+ begin
36
+ errno = kls.const_get(:Errno)
37
+ @errno_lookup_table[errno] = kls
38
+ rescue NoMethodError, NameError
39
+ end
40
+ end
41
+
42
+ @lookup_table = {}
43
+
44
+ # Populate the FFI::Status lookup_table
45
+ (FFI::Status.symbols - [:errno]).each do |status|
46
+ message = FFI.mosquitto_strerror(status)
47
+ message.gsub!(/\.\s*\Z/, '')
48
+ kls = Class.new(Error) { define_method(:status_message) { message } }
49
+ @lookup_table[status] = kls
50
+ const_set Util.const_name(status), kls
51
+ end
52
+
53
+ # Custom static class to use for timeouts
54
+ Timeout = Class.new(Error) { define_method(:status_message) { "timed out" } }
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,69 @@
1
+
2
+ module Mosq
3
+
4
+ # Helper functions for this library.
5
+ # @api private
6
+ module Util
7
+ module_function
8
+
9
+ def const_name(lowercase_name)
10
+ lowercase_name.to_s.gsub(/((?:\A\w)|(?:_\w))/) { |x| x[-1].upcase }
11
+ end
12
+
13
+ def error_check(action, status)
14
+ return if status == :success
15
+ raise Mosq::FFI::Error.lookup(status), "while #{action}"
16
+ end
17
+
18
+ def null_check(action, obj)
19
+ return unless obj.nil?
20
+ raise Mosq::FFI::Error, "while #{action} - got unexpected null"
21
+ end
22
+
23
+ def mem_ptr(size, count: 1, clear: true, release: true)
24
+ ptr = ::FFI::MemoryPointer.new(size, count, clear)
25
+ ptr.autorelease = false unless release
26
+ ptr
27
+ end
28
+
29
+ def strdup_ptr(str, **kwargs)
30
+ str = str + "\x00"
31
+ ptr = mem_ptr(str.bytesize, **kwargs)
32
+ ptr.write_string(str)
33
+ ptr
34
+ end
35
+
36
+ def strdup_ary_ptr(ary, **kwargs)
37
+ ptr = mem_ptr(:pointer, count: ary.size)
38
+ ary.each_with_index do |str, i|
39
+ cursor = (ptr + i * ::FFI::TypeDefs[:pointer].size)
40
+ cursor.write_pointer(strdup_ptr(str, **kwargs))
41
+ end
42
+ ptr
43
+ end
44
+
45
+ def connection_info(uri=nil, **overrides)
46
+ info = {
47
+ ssl: false,
48
+ host: "localhost",
49
+ port: 1883,
50
+ }
51
+ if uri
52
+ # TODO: support IPv6
53
+ pattern = %r{\A(?<schema>mqtts?)://((?<username>[^:]+):(?<password>[^@]+)@)?(?<host>[^:]+)(:(?<port>\d+))?\Z}
54
+ match = pattern.match(uri)
55
+ if match
56
+ info[:ssl] = ("mqtts" == match[:schema])
57
+ info[:host] = match[:host]
58
+ info[:port] = match[:port] ? Integer(match[:port]) : (info[:ssl] ? 8883 : 1883)
59
+ info[:username] = match[:username] if match[:username]
60
+ info[:password] = match[:password] if match[:password]
61
+ else
62
+ info[:host] = uri
63
+ end
64
+ end
65
+ info.merge(overrides)
66
+ end
67
+ end
68
+
69
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mosq
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joe McIlvain
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.9.8
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.9'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.9.8
33
+ - !ruby/object:Gem::Dependency
34
+ name: bundler
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.6'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.6'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '10.3'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '10.3'
61
+ - !ruby/object:Gem::Dependency
62
+ name: pry
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.9'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.9'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rspec-its
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1.0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: fivemat
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.3'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '1.3'
117
+ description: A Ruby MQTT client library based on FFI bindings for libmosquitto.
118
+ email: joe.eli.mac@gmail.com
119
+ executables: []
120
+ extensions:
121
+ - ext/mosq/Rakefile
122
+ extra_rdoc_files: []
123
+ files:
124
+ - LICENSE
125
+ - README.md
126
+ - ext/mosq/Rakefile
127
+ - lib/mosq.rb
128
+ - lib/mosq/client.rb
129
+ - lib/mosq/client/bucket.rb
130
+ - lib/mosq/ffi.rb
131
+ - lib/mosq/ffi/error.rb
132
+ - lib/mosq/util.rb
133
+ homepage: https://github.com/jemc/ruby-mosq
134
+ licenses:
135
+ - MIT
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubyforge_project:
153
+ rubygems_version: 2.2.2
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: mosq
157
+ test_files: []
158
+ has_rdoc: