mqtt-core 0.0.1.ci.release

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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/lib/mqtt/core/client/acknowledgement.rb +31 -0
  3. data/lib/mqtt/core/client/client_id_generator.rb +19 -0
  4. data/lib/mqtt/core/client/connection.rb +193 -0
  5. data/lib/mqtt/core/client/enumerable_subscription.rb +224 -0
  6. data/lib/mqtt/core/client/filesystem_session_store.rb +197 -0
  7. data/lib/mqtt/core/client/memory_session_store.rb +84 -0
  8. data/lib/mqtt/core/client/qos0_session_store.rb +56 -0
  9. data/lib/mqtt/core/client/qos2_session_store.rb +45 -0
  10. data/lib/mqtt/core/client/qos_tracker.rb +119 -0
  11. data/lib/mqtt/core/client/retry_strategy.rb +63 -0
  12. data/lib/mqtt/core/client/session.rb +195 -0
  13. data/lib/mqtt/core/client/session_store.rb +128 -0
  14. data/lib/mqtt/core/client/socket_factory.rb +268 -0
  15. data/lib/mqtt/core/client/subscription.rb +109 -0
  16. data/lib/mqtt/core/client/uri.rb +77 -0
  17. data/lib/mqtt/core/client.rb +700 -0
  18. data/lib/mqtt/core/packet/connect.rb +39 -0
  19. data/lib/mqtt/core/packet/publish.rb +45 -0
  20. data/lib/mqtt/core/packet/subscribe.rb +190 -0
  21. data/lib/mqtt/core/packet/unsubscribe.rb +21 -0
  22. data/lib/mqtt/core/packet.rb +168 -0
  23. data/lib/mqtt/core/type/binary.rb +35 -0
  24. data/lib/mqtt/core/type/bit_flags.rb +89 -0
  25. data/lib/mqtt/core/type/boolean_byte.rb +48 -0
  26. data/lib/mqtt/core/type/fixed_int.rb +43 -0
  27. data/lib/mqtt/core/type/list.rb +41 -0
  28. data/lib/mqtt/core/type/password.rb +36 -0
  29. data/lib/mqtt/core/type/properties.rb +124 -0
  30. data/lib/mqtt/core/type/reason_codes.rb +60 -0
  31. data/lib/mqtt/core/type/remaining.rb +30 -0
  32. data/lib/mqtt/core/type/shape.rb +177 -0
  33. data/lib/mqtt/core/type/sub_type.rb +34 -0
  34. data/lib/mqtt/core/type/utf8_string.rb +39 -0
  35. data/lib/mqtt/core/type/utf8_string_pair.rb +29 -0
  36. data/lib/mqtt/core/type/var_int.rb +56 -0
  37. data/lib/mqtt/core/version.rb +10 -0
  38. data/lib/mqtt/core.rb +5 -0
  39. data/lib/mqtt/errors.rb +39 -0
  40. data/lib/mqtt/logger.rb +92 -0
  41. data/lib/mqtt/open.rb +239 -0
  42. data/lib/mqtt/options.rb +59 -0
  43. data/lib/mqtt/version.rb +6 -0
  44. data/lib/mqtt.rb +3 -0
  45. data/lib/patches/openssl.rb +19 -0
  46. metadata +98 -0
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Sugar for logging
4
+ require 'logger'
5
+ require 'delegate'
6
+
7
+ module MQTT
8
+ # The MQTT Logger
9
+ module Logger
10
+ # @!visibility private
11
+ # Adds classname info as progname to each log message
12
+ class InstanceLogger < SimpleDelegator
13
+ def initialize(obj)
14
+ @obj = obj
15
+ super(Logger.log)
16
+ end
17
+
18
+ %i[debug info warn error fatal].each do |lvl|
19
+ lvl_check = :"#{lvl}?"
20
+ lvl_const = ::Logger.const_get(lvl.upcase)
21
+ define_method(lvl) do |message = nil, &block|
22
+ next unless __get_obj__.public_send(lvl_check)
23
+
24
+ add(lvl_const, message, &block)
25
+ end
26
+ end
27
+
28
+ def add(severity, message = nil, &block)
29
+ if block
30
+ __get_obj__.add(severity, nil, @obj.log_name) { convert_message(block.call) }
31
+ else
32
+ __get_obj__.add(severity, convert_message(message), @obj.log_name)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def __get_obj__
39
+ Logger.log
40
+ end
41
+
42
+ def convert_message(msg)
43
+ case msg
44
+ when Exception
45
+ ["#{msg.class.name}: #{msg.message}", *(__get_obj__.debug? ? msg.backtrace : [])].join("\n")
46
+ else
47
+ msg
48
+ end
49
+ end
50
+ end
51
+
52
+ class << self
53
+ # @!visibility private
54
+ def configure(file: nil, shift_age: nil, shift_size: nil, level: nil)
55
+ shift_age = Integer(shift_age) if shift_age
56
+ shift_size = Integer(shift_size) if shift_size
57
+
58
+ device = [file, shift_age, shift_size].compact
59
+ if device.any?
60
+ send(:log=, *device, level: (level || defined?(@log) ? @log.level : ::Logger::INFO))
61
+ elsif level
62
+ log.level = level
63
+ end
64
+ end
65
+
66
+ # @!visibility private
67
+ # Set the log device
68
+ def log=(*device, level: defined?(@log) ? @log.level : ::Logger::INFO)
69
+ @log = ::Logger.new(*device, level:)
70
+ end
71
+
72
+ # @!visibility private
73
+ # The actual logger
74
+ # @return [::Logger]
75
+ # @example
76
+ # MQTT::Logger.log.warn! # set level to WARN
77
+ def log
78
+ @log ||= send(:log=, $stdout).tap { $stdout.sync = true }
79
+ end
80
+ end
81
+
82
+ # @!visibility private
83
+ def log
84
+ @log ||= InstanceLogger.new(self)
85
+ end
86
+
87
+ # @!visibility private
88
+ def log_name
89
+ self.class.name
90
+ end
91
+ end
92
+ end
data/lib/mqtt/open.rb ADDED
@@ -0,0 +1,239 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'core/client'
4
+ require_relative 'options'
5
+
6
+ # MQTT top level namespace
7
+ module MQTT
8
+ class << self
9
+ include Options
10
+
11
+ # Construct and Configure an MQTT client
12
+ #
13
+ # If a block is provided, yields the client and ensures disconnection.
14
+ # Otherwise, the client is returned directly.
15
+ #
16
+ # In either case the client is provided in `:configure` state and not yet connected to the broker.
17
+ #
18
+ # @note All scalar-valued options (String, Integer, Float, Boolean) can be provided as URI query parameters
19
+ # unless `ignore_uri_params: true` is set. URI parameters override explicit keyword arguments.
20
+ #
21
+ # @overload open(*io_args, **client_opts)
22
+ # @param io_args see {Core::Client::SocketFactory}. Typically an `mqtt[s]://` or `unix://` URI
23
+ # @param [Hash<Symbol>] client_opts
24
+ # @option client_opts [Boolean] ignore_uri_params (false)
25
+ # When true, URI query parameters are ignored. Use where URI input is untrusted.
26
+ # @option client_opts [Hash<Symbol>] **socket_factory options passed to {Core::Client::SocketFactory}
27
+ #
28
+ # - `host`, `port`, `local_addr`, `local_port`, `scheme`, `connect_timeout`, `ssl_(.*)`
29
+ # @option client_opts [Hash<Symbol>] **logger options passed to {MQTT::Logger.configure}
30
+ #
31
+ # - `log_(.*)`
32
+ # @option client_opts [Integer] protocol_version ([5, 3]) MQTT protocol major version
33
+ #
34
+ # - 5 requires `mqtt/v5` gem
35
+ # - 3 requires `mqtt/v3` gem.
36
+ # - If not set, v5 is preferred over v3 based on the ability to load the requisite gem.
37
+ # @option client_opts [String, nil] client_id ('') Session identifier
38
+ #
39
+ # - Default empty string implies anonymous or server-assigned ids
40
+ # - Set to nil to force generation of a random id if the broker does not support anonymous ids
41
+ # - {Core::Client::FilesystemSessionStore} requires a fixed, non-empty client_id
42
+ # @option client_opts [Integer, nil] session_expiry_interval Duration in seconds before the session
43
+ # expires after disconnect
44
+ #
45
+ # - Set to nil to use maximum (130+ years) expiry
46
+ # @option client_opts [String, Pathname] session_base_dir Base directory for
47
+ # {Core::Client::FilesystemSessionStore}
48
+ # @option client_opts [Core::Client::SessionStore, Class, Proc] session_store SessionStore instance, Class,
49
+ # or factory
50
+ #
51
+ # - Class/Proc is called with `client_id` and `session_(.*)` options
52
+ # - If not set
53
+ # - with `:session_base_dir` a {Core::Client::FilesystemSessionStore} is constructed
54
+ # - with `:session_expiry_interval` a {Core::Client::MemorySessionStore} is constructed
55
+ # - defaults to constructing a {Core::Client::Qos0SessionStore}
56
+ #
57
+ # @option client_opts [Core::Client::RetryStrategy, Class, Proc, Boolean] retry_strategy RetryStrategy instance,
58
+ # Class, or factory. Determines how to retry after connection dropouts.
59
+ #
60
+ # - Class/Proc is called with other `retry_(.*)` options
61
+ # - Explicitly false or nil disables retries
62
+ # - If not set
63
+ # - If other `retry_(.*)` options are present, they are used to construct a {Core::Client::RetryStrategy}
64
+ # - If the `:session_store` option resolves to only support QoS0, then retries are disabled
65
+ # - Otherwise, a {Core::Client::RetryStrategy} is constructed with default settings
66
+ # @option client_opts [Integer] keep_alive (60)
67
+ # Duration in seconds between PING packets sent to the broker to keep the connection alive.
68
+ # @option client_opts [Hash<Symbol>] **topic_alias options to build a {V5::TopicAlias::Manager} (V5 only)
69
+ #
70
+ # - `topic_aliases` TopicAlias::Manager instance, Class, or factory. (default {V5::TopicAlias::Manager})
71
+ # - `policy` TopicAlias::Policy instance, Class, or factory. (default {V5::TopicAlias::LRUPolicy})
72
+ # - `send_maximum` Integer maximum number of topic aliases to send to the broker.
73
+ # Default is the broker limit if a policy is set, otherwise 0, effectively disabling outgoing topic
74
+ # aliases.
75
+ #
76
+ # @option client_opts [Hash<Symbol>] **connect other CONNECT packet options
77
+ # @yield [client] Yields the configured client for use within the block
78
+ # @yieldparam client [Core::Client] the MQTT client instance {V5::Client} or {V3::Client}
79
+ # @return [Core::Client] the client if no block is given
80
+ # @return [void] if a block is given
81
+ # @raise [LoadError] if no suitable MQTT gem can be found
82
+ # @example Default usage - entirely configured via URI from environment variable
83
+ # # ENV['MQTT_SERVER'] => 'mqtt://localhost'
84
+ # MQTT.open do |client|
85
+ # client.connect(will_topic: 'status', will_payload: 'offline')
86
+ # client.publish(topic, payload)
87
+ # end
88
+ # @example Untrusted URI input, only directly set options are used
89
+ # MQTT.open(untrusted_uri, ignore_uri_params: true, ssl_min_version: :TLSv1_2) do |client|
90
+ # client.connect
91
+ # #...
92
+ # end
93
+ # @example Force v5 protocol
94
+ # MQTT.open(mqtt_uri, protocol_version: 5) do |client|
95
+ # client.connect
96
+ # # ...
97
+ # end
98
+ # @example Need Qos1/2 guarantees only as long as the process is running
99
+ # MQTT.open(mqtt_uri, session_expiry_interval: nil) do |client|
100
+ # client.session_store # => Core::Client::MemorySessionStore
101
+ # client.publish(topic, payload, qos: 2)
102
+ # # ...
103
+ # end
104
+ # @example Full Qos1/2 guarantees including over process restarts within one day
105
+ # mqtt_uri = "mqtt://host.example.com/?client_id=#{ENV['HOSTNAME']}"
106
+ # MQTT.open(mqtt_uri, session_base_dir: '/data/mqtt', session_expiry_interval: 86400) do |client|
107
+ # client.session_store # => Core::Client::FilesystemSessionStore
108
+ # client.on_birth do
109
+ # client.subscribe(*topics, max_qos: 2).async { |topic, payload| process(topic, payload) }
110
+ # end
111
+ # sleep # until process terminates
112
+ # end
113
+ #
114
+ def open(*io_args, async: false, **client_opts, &)
115
+ # SocketFactory will claim its keyword options, client_opts is left with the remaining options
116
+ sf = Core::Client::SocketFactory.create(*io_args, options: client_opts)
117
+ client_opts.merge!(sf.query_params) if sf.respond_to?(:query_params)
118
+ MQTT::Logger.configure(**slice_opts!(client_opts, /^log_(.*)$/))
119
+
120
+ class_opts = { async: async, protocol_version: client_opts.delete(:protocol_version) || %w[5 3] }
121
+ client_class = client_class(**class_opts)
122
+
123
+ session_store = session_store(**slice_opts!(client_opts, :client_id, :session_store, /^session_(.*)$/))
124
+
125
+ open_opts = {
126
+ **retry_strategy(max_qos: session_store.max_qos, **slice_opts!(client_opts, :retry_strategy, /^retry_(.*)$/)),
127
+ **slice_opts!(client_opts, :keep_alive)
128
+ }
129
+
130
+ v5_specific_options(client_class.protocol_version, client_opts, open_opts)
131
+
132
+ client_class.open(sf, session_store:, **client_opts, **open_opts, &)
133
+ end
134
+
135
+ # Utility to test broker connectivity
136
+ #
137
+ # Connect to the broker, outputting status information
138
+ # @see open
139
+ def test(*io_args, **client_opts)
140
+ self.open(*io_args, **client_opts) do |client|
141
+ client.configure_retry(false)
142
+ puts "Client: #{client}"
143
+ puts " Session: #{client.session_store}"
144
+ client.connect
145
+ puts " Status: #{client.status}"
146
+ end
147
+ end
148
+
149
+ # As per {open} but returns an Async client that uses Fibers for concurrency
150
+ #
151
+ # If not using the block form, {Core::Client#connect} and later methods must be called within the reactor.
152
+ # @example Async client usage
153
+ # require 'async'
154
+ # require 'mqtt/core'
155
+ #
156
+ # client = MQTT.async_open('localhost')
157
+ # Sync do
158
+ # client.connect
159
+ # client.publish(topic, payload)
160
+ # end
161
+ # @yield [client] Yields the configured client for use within the block
162
+ # @yieldparam client [Core::Client] the MQTT client instance {V5::Async::Client} or {V3::Async::Client}
163
+ # @return [Core::Client] the client if no block is given
164
+ # @return [void] if a block is given
165
+ def async_open(*io_args, **client_opts, &)
166
+ self.open(*io_args, async: true, **client_opts, &)
167
+ end
168
+
169
+ private
170
+
171
+ def client_class(protocol_version:, async:)
172
+ protocol_version = [protocol_version] unless protocol_version.is_a?(Array)
173
+ protocol_version.map(&:to_s).each do |v|
174
+ raise ArgumentError, "Invalid mqtt-version #{v}" unless %w[3 5 31 3.1 50 5.0 3.1.1 311].include?(v)
175
+
176
+ v = v[0]
177
+ require async ? "mqtt/v#{v}/async/client" : "mqtt/v#{v}"
178
+ return Class.const_get("MQTT::V#{v}::#{'Async::' if async}Client")
179
+ rescue LoadError => _e
180
+ # warn e
181
+ end
182
+ raise LoadError, "Could not find MQTT gem for protocol versions: #{protocol_version}"
183
+ end
184
+
185
+ def session_store(session_store: nil, **opts)
186
+ return session_store.call(**opts) if session_store.respond_to?(:call)
187
+ return session_store.new(**opts) if session_store.is_a?(Class)
188
+ return session_store if session_store # expecting something that quacks like Core::Client::SessionStore
189
+
190
+ return Core::Client.file_store(**opts) if opts.key?(:base_dir)
191
+ return Core::Client.memory_store(**opts) if opts.key?(:expiry_interval)
192
+
193
+ Core::Client.qos0_store(**opts)
194
+ end
195
+
196
+ # @return [RetryStrategy] URI parameters (prefixed 'retry_') override provided defaults
197
+ # @see open
198
+ def retry_strategy(max_qos:, retry_strategy: max_qos.positive? || :__not_set__, **options)
199
+ return {} if retry_strategy == :__not_set__ && options.empty?
200
+ return { retry_strategy: retry_strategy.call(**options) } if retry_strategy.respond_to?(:call)
201
+ return { retry_strategy: retry_strategy.new(**options) } if retry_strategy.is_a?(Class)
202
+ return { retry_strategy: false } if %w[false 0 no n nil].include?(retry_strategy.to_s.downcase)
203
+
204
+ { retry_strategy: Core::Client.retry_strategy(**options) }
205
+ end
206
+
207
+ # extract from client_opts into open_opts
208
+ def v5_specific_options(protocol_version, client_opts, open_opts)
209
+ ta_keys = [:topic_aliases, :topic_alias_maximum, /^topic_alias_(.*)$/]
210
+ v5_opts(protocol_version, client_opts, open_opts, *ta_keys) { |**ta_opts| topic_alias_opts(**ta_opts) }
211
+ end
212
+
213
+ def v5_opts(protocol_version, client_opts, open_opts, *slice_keys, &block)
214
+ v5_opts = slice_opts!(client_opts, *slice_keys)
215
+ if protocol_version == 5
216
+ open_opts.merge!(block.call(**v5_opts))
217
+ else
218
+ MQTT::Logger.log.warn "Ignoring #{slice_keys.last} options for protocol version #{protocol_version}"
219
+ end
220
+ end
221
+
222
+ def topic_alias_opts(
223
+ topic_aliases: V5::TopicAlias::Manager, topic_alias_maximum: nil,
224
+ policy: nil, send_maximum: :__not_set__, **
225
+ )
226
+ # Use broker limit if we have a policy but no send limit
227
+ send_maximum = nil if send_maximum == :__not_set__
228
+ send_maximum = coerce_integer(send_maximum:) if send_maximum
229
+
230
+ policy = policy.call(**) if policy.respond_to?(:call)
231
+ policy = policy.new(**) if policy.is_a?(Class)
232
+
233
+ topic_aliases = topic_aliases.call(policy:, send_maximum:, **) if topic_aliases.respond_to?(:call)
234
+ topic_aliases = topic_aliases.new(policy:, send_maximum:, **) if topic_aliases.is_a?(Class)
235
+
236
+ { topic_aliases: topic_aliases, topic_alias_maximum: }.compact
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MQTT
4
+ # @!visibility private
5
+ module Options
6
+ private
7
+
8
+ # Slice out a set of options, removing them from the original hash
9
+ # @param [Hash] opts
10
+ # @param [Array<Symbol,Regexp>] keys
11
+ # - Symbol: exact match
12
+ # - Regexp: match against key, using first matching group to name the option (eg remove prefix/suffix)
13
+ # @return [Hash]
14
+ def slice_opts!(opts, *keys) # rubocop:disable Metrics/AbcSize
15
+ {}.tap do |slice|
16
+ opts.delete_if do |o, v|
17
+ keys.any? do |k|
18
+ if k.is_a?(Regexp)
19
+ k.match(o).tap { slice[(it[1] || o).to_sym] = v if it }
20
+ else
21
+ (k.to_sym == o).tap { slice[o] = v if it }
22
+ end
23
+ end
24
+ end
25
+ slice.each { |k, v| slice[k] = yield(k, v) } if block_given?
26
+ end
27
+ end
28
+
29
+ def coerce_boolean(_key, value)
30
+ return value if [true, false, nil].include?(value)
31
+ return false if value.to_s.match?(/^(false|f|no|n|0)$/i)
32
+
33
+ !!value
34
+ end
35
+
36
+ def coerce_integer(key = nil, value = nil, **opts)
37
+ key, value = opts.first if opts.size == 1
38
+ coerce(key, value, type: :Integer)
39
+ end
40
+
41
+ def coerce_float(key, value)
42
+ coerce(key, value, type: :Float)
43
+ end
44
+
45
+ # @!visibility private
46
+ def coerce(key, value, type: :Integer)
47
+ return value unless value
48
+
49
+ case type
50
+ when :Integer
51
+ Integer(value)
52
+ when :Float
53
+ Float(value)
54
+ end
55
+ rescue ArgumentError => e
56
+ raise ArgumentError, "#{key}: #{e.message}"
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MQTT
4
+ # The version number shared across MQTT gems (mqtt-core, mqtt-v3, mqtt-v5)
5
+ VERSION = '0.0.1'
6
+ end
data/lib/mqtt.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ raise LoadError, 'this is not njh/ruby-mqtt. Please require "mqtt/core" , "mqtt/v5" or "mqtt/v3"'
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ if Gem::Version.new(OpenSSL::VERSION) < Gem::Version.new('3.3.0')
6
+ module OpenSSL
7
+ module SSL
8
+ # Socket patches
9
+ class SSLSocket
10
+ # https://github.com/ruby/openssl/pull/771
11
+ unless method_defined?(:readbyte)
12
+ def readbyte
13
+ getbyte or raise EOFError
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mqtt-core
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.ci.release
5
+ platform: ruby
6
+ authors:
7
+ - Grant Gardner
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: concurrent_monitor
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ description: ''
27
+ email:
28
+ - grant@lastweekend.com.au
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/mqtt.rb
34
+ - lib/mqtt/core.rb
35
+ - lib/mqtt/core/client.rb
36
+ - lib/mqtt/core/client/acknowledgement.rb
37
+ - lib/mqtt/core/client/client_id_generator.rb
38
+ - lib/mqtt/core/client/connection.rb
39
+ - lib/mqtt/core/client/enumerable_subscription.rb
40
+ - lib/mqtt/core/client/filesystem_session_store.rb
41
+ - lib/mqtt/core/client/memory_session_store.rb
42
+ - lib/mqtt/core/client/qos0_session_store.rb
43
+ - lib/mqtt/core/client/qos2_session_store.rb
44
+ - lib/mqtt/core/client/qos_tracker.rb
45
+ - lib/mqtt/core/client/retry_strategy.rb
46
+ - lib/mqtt/core/client/session.rb
47
+ - lib/mqtt/core/client/session_store.rb
48
+ - lib/mqtt/core/client/socket_factory.rb
49
+ - lib/mqtt/core/client/subscription.rb
50
+ - lib/mqtt/core/client/uri.rb
51
+ - lib/mqtt/core/packet.rb
52
+ - lib/mqtt/core/packet/connect.rb
53
+ - lib/mqtt/core/packet/publish.rb
54
+ - lib/mqtt/core/packet/subscribe.rb
55
+ - lib/mqtt/core/packet/unsubscribe.rb
56
+ - lib/mqtt/core/type/binary.rb
57
+ - lib/mqtt/core/type/bit_flags.rb
58
+ - lib/mqtt/core/type/boolean_byte.rb
59
+ - lib/mqtt/core/type/fixed_int.rb
60
+ - lib/mqtt/core/type/list.rb
61
+ - lib/mqtt/core/type/password.rb
62
+ - lib/mqtt/core/type/properties.rb
63
+ - lib/mqtt/core/type/reason_codes.rb
64
+ - lib/mqtt/core/type/remaining.rb
65
+ - lib/mqtt/core/type/shape.rb
66
+ - lib/mqtt/core/type/sub_type.rb
67
+ - lib/mqtt/core/type/utf8_string.rb
68
+ - lib/mqtt/core/type/utf8_string_pair.rb
69
+ - lib/mqtt/core/type/var_int.rb
70
+ - lib/mqtt/core/version.rb
71
+ - lib/mqtt/errors.rb
72
+ - lib/mqtt/logger.rb
73
+ - lib/mqtt/open.rb
74
+ - lib/mqtt/options.rb
75
+ - lib/mqtt/version.rb
76
+ - lib/patches/openssl.rb
77
+ licenses:
78
+ - MIT
79
+ metadata:
80
+ rubygems_mfa_required: 'true'
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '3.4'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubygems_version: 3.6.9
96
+ specification_version: 4
97
+ summary: Ruby MQTT Core
98
+ test_files: []