mosq 0.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,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: