mqtt-v5 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.
- checksums.yaml +7 -0
- data/lib/mqtt/v5/async/client.rb +27 -0
- data/lib/mqtt/v5/client/authenticator.rb +83 -0
- data/lib/mqtt/v5/client/connection.rb +192 -0
- data/lib/mqtt/v5/client/session.rb +45 -0
- data/lib/mqtt/v5/client.rb +61 -0
- data/lib/mqtt/v5/errors.rb +19 -0
- data/lib/mqtt/v5/packet/auth.rb +57 -0
- data/lib/mqtt/v5/packet/connack.rb +110 -0
- data/lib/mqtt/v5/packet/connect.rb +130 -0
- data/lib/mqtt/v5/packet/disconnect.rb +78 -0
- data/lib/mqtt/v5/packet/ping_req.rb +20 -0
- data/lib/mqtt/v5/packet/ping_resp.rb +20 -0
- data/lib/mqtt/v5/packet/pub_ack.rb +60 -0
- data/lib/mqtt/v5/packet/pub_comp.rb +53 -0
- data/lib/mqtt/v5/packet/pub_rec.rb +60 -0
- data/lib/mqtt/v5/packet/pub_rel.rb +53 -0
- data/lib/mqtt/v5/packet/publish.rb +122 -0
- data/lib/mqtt/v5/packet/reason_code.rb +112 -0
- data/lib/mqtt/v5/packet/sub_ack.rb +66 -0
- data/lib/mqtt/v5/packet/subscribe.rb +87 -0
- data/lib/mqtt/v5/packet/unsub_ack.rb +59 -0
- data/lib/mqtt/v5/packet/unsubscribe.rb +112 -0
- data/lib/mqtt/v5/packet.rb +147 -0
- data/lib/mqtt/v5/packets.rb +25 -0
- data/lib/mqtt/v5/topic_alias/cache.rb +87 -0
- data/lib/mqtt/v5/topic_alias/frequency_weighted_policy.rb +50 -0
- data/lib/mqtt/v5/topic_alias/length_weighted_policy.rb +20 -0
- data/lib/mqtt/v5/topic_alias/lru_policy.rb +36 -0
- data/lib/mqtt/v5/topic_alias/manager.rb +143 -0
- data/lib/mqtt/v5/topic_alias/policy.rb +34 -0
- data/lib/mqtt/v5/topic_alias/weighted_policy.rb +48 -0
- data/lib/mqtt/v5/topic_alias.rb +27 -0
- data/lib/mqtt/v5/version.rb +11 -0
- data/lib/mqtt/v5.rb +4 -0
- metadata +88 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0a5e261413c2be4f3d16bbbd3ee4047b851617ce7316fa26860e16206369b23c
|
|
4
|
+
data.tar.gz: da4064437dc46ac32962b5df6353d4c5e25ec017ea0eda8a70e9eb11aa1afc8e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b012587302e73b990c2d8f4879ec1cc1f8de601bd2c272cdf4d44d39dbf73bc5215424edebdcea57e52acd00212f0926680be1a2619c92ec36c2e06b9e115f70
|
|
7
|
+
data.tar.gz: fd2bff72aac401f3dc37dfa86fa1498b4440771fffb5f7c681b9fe2d7369b175609eef3f325ee347673da0c5f6ecdb654f2cc4018c3eddcb8540514355ebd4df
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'async'
|
|
4
|
+
require_relative '../client'
|
|
5
|
+
|
|
6
|
+
module MQTT
|
|
7
|
+
module V5
|
|
8
|
+
module Async
|
|
9
|
+
# An Async {MQTT::Core::Client} for MQTT 5.0
|
|
10
|
+
class Client < MQTT::V5::Client
|
|
11
|
+
class << self
|
|
12
|
+
# Create a new {Client}
|
|
13
|
+
def open(*io_args, **run_args, &)
|
|
14
|
+
raise FiberError, 'No async reactor' unless block_given? || ::Async::Task.current?
|
|
15
|
+
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @!visibility private
|
|
20
|
+
def mqtt_monitor
|
|
21
|
+
async_monitor
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MQTT
|
|
4
|
+
module V5
|
|
5
|
+
class Client < MQTT::Core::Client
|
|
6
|
+
# Enhanced authentication
|
|
7
|
+
# @abstract
|
|
8
|
+
class Authenticator
|
|
9
|
+
class << self
|
|
10
|
+
# @param [String] auth_method
|
|
11
|
+
# @param [Class|Authenticator] authenticator
|
|
12
|
+
def register(auth_method, authenticator)
|
|
13
|
+
@authenticators ||= {}
|
|
14
|
+
@authenticators[auth_method] = authenticator
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @param [String<UTF8>|Class|Authenticator] auth_method
|
|
18
|
+
# * String will lookup a registered authenticator (see #{register} )
|
|
19
|
+
# * Class will be used to instantiate an Authenticator
|
|
20
|
+
# * Authenticator (or something that quacks like one) will be used directly
|
|
21
|
+
# @return [Authenticator]
|
|
22
|
+
# @raise [Error] if authentication_method is not valid
|
|
23
|
+
def factory(auth_method)
|
|
24
|
+
return nil unless auth_method
|
|
25
|
+
|
|
26
|
+
if auth_method.is_a?(String)
|
|
27
|
+
auth_method = @authenticators.fetch do
|
|
28
|
+
raise ProtocolError, "Unknown authentication method #{auth_method}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
return auth_method.new if auth_method.is_a?(Class)
|
|
33
|
+
return auth_method if %i[start continue success failed].all? { |m| auth_method.respond_to?(m) }
|
|
34
|
+
|
|
35
|
+
raise Error, "Invalid Authenticator #{auth_method}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Start authentication
|
|
40
|
+
# @!method start(authentication_method:, authentication_data: nil, **connect_props)
|
|
41
|
+
# @param [String] authentication_method
|
|
42
|
+
# @param [String] authentication_data
|
|
43
|
+
# @param [Hash<Symbol>] connect_props additional properties for connect packet
|
|
44
|
+
# @return [Hash<Symbol>] properties to merge into the connect data
|
|
45
|
+
# typically will include :authentication_data
|
|
46
|
+
# can include other connect packet properties
|
|
47
|
+
|
|
48
|
+
# Initiate re-authentication
|
|
49
|
+
# @!method reauthenticate(authentication_method:, authentication_data: nil, **auth_props)
|
|
50
|
+
# @param [String] authentication_method
|
|
51
|
+
# @param [String,nil] authentication_data
|
|
52
|
+
# @param [Hash<Symbol>] auth_props additional properties supplied to reauthenticate
|
|
53
|
+
# @return [Hash<Symbol>] properties to merge into the auth data
|
|
54
|
+
# typically will include :authentication_data
|
|
55
|
+
# can include other auth packet properties
|
|
56
|
+
# @note this need not be implemented if re-authentication is not required
|
|
57
|
+
|
|
58
|
+
# Continue authentication
|
|
59
|
+
# @!method continue(authentication_method:, authentication_data: nil, **auth_props)
|
|
60
|
+
# @param [String] authentication_method
|
|
61
|
+
# @param [String] authentication_data
|
|
62
|
+
# @param [Hash<Symbol>] auth_props additional auth properties (from auth packet)
|
|
63
|
+
# @return [Hash<Symbol>] properties to merge into the auth packet
|
|
64
|
+
# typically will include :authentication_data
|
|
65
|
+
# can include other auth packet properties
|
|
66
|
+
|
|
67
|
+
# Completion of successful authentication
|
|
68
|
+
# @!method success(authentication_method:, authentication_data: nil, **auth_props)
|
|
69
|
+
# @param [String] authentication_method
|
|
70
|
+
# @param [String] authentication_data
|
|
71
|
+
# @param [Hash<Symbol>] auth_props additional auth properties (from connack or auth completion packet)
|
|
72
|
+
# @return [void]
|
|
73
|
+
|
|
74
|
+
# Notification of failed authentication
|
|
75
|
+
# @!method failed(reason_code:, **properties)
|
|
76
|
+
# @param [Integer] reason_code
|
|
77
|
+
# @param [Hash<Symbol>] properties of the failed connack or disconnect packet
|
|
78
|
+
# @return [void]
|
|
79
|
+
# @note this is not guaranteed to be called
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mqtt/core/client'
|
|
4
|
+
require_relative '../topic_alias'
|
|
5
|
+
|
|
6
|
+
module MQTT
|
|
7
|
+
module V5
|
|
8
|
+
class Client < MQTT::Core::Client
|
|
9
|
+
# Client protocol for MQTT 5.0
|
|
10
|
+
class Connection < MQTT::Core::Client::Connection # rubocop:disable Metrics/ClassLength
|
|
11
|
+
def initialize(**)
|
|
12
|
+
super
|
|
13
|
+
@qos_send_condition = new_condition
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def publish(qos: 0, topic_alias: true, **publish, &)
|
|
17
|
+
decrement_send_quota if qos.positive?
|
|
18
|
+
topic_alias = topic_alias.positive? if topic_alias.is_a?(Integer)
|
|
19
|
+
publish[:assign_alias] = topic_alias
|
|
20
|
+
begin
|
|
21
|
+
session.publish(qos:, **publish, &)
|
|
22
|
+
rescue StandardError
|
|
23
|
+
increment_send_quota if qos.positive?
|
|
24
|
+
raise
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def send_packet(packet)
|
|
29
|
+
topic_aliases&.handle_outgoing(packet) if packet&.packet_name == :publish
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def disconnect(exception = nil, **disconnect)
|
|
34
|
+
if exception
|
|
35
|
+
disconnect.merge!({ reason_code: 0x04, reason_string: "#{exception.class.name}: #{exception.message}" })
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
super(**disconnect)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def reauthenticate(**auth, &)
|
|
42
|
+
raise ProtocolError, 'Not connected with extended auth' unless @auth
|
|
43
|
+
|
|
44
|
+
# reauthenticate sets authentication_method/data in auth data
|
|
45
|
+
send_packet(auth_packet(reason_code: 0x19, **@auth.reauthenticate(**auth)), &)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def_delegators :session, :clean_start, :session_expiry_interval, :max_packet_id, :stored_packet?, :session_store
|
|
51
|
+
def_delegators :client, :topic_aliases, :auth_ack
|
|
52
|
+
|
|
53
|
+
def connect_packet(**connect)
|
|
54
|
+
super.tap do |p|
|
|
55
|
+
@qos_receive_quota = p.receive_maximum || max_packet_id
|
|
56
|
+
topic_aliases&.clear_incoming!(p)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def connect_data(**connect)
|
|
61
|
+
super.tap { |data| data.merge!(setup_authentication(**connect)) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def setup_authentication(authentication_method: nil, **connect)
|
|
65
|
+
return {} unless authentication_method
|
|
66
|
+
|
|
67
|
+
@auth = Authenticator.factory(authentication_method)
|
|
68
|
+
@auth.start(authentication_method:, **connect)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def complete_connection(received_packet)
|
|
72
|
+
super(complete_authentication(received_packet))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def complete_authentication(packet)
|
|
76
|
+
while packet&.packet_name == :auth
|
|
77
|
+
raise ProtocolError, 'Unexpected auth packet' unless @auth
|
|
78
|
+
|
|
79
|
+
packet.continue!
|
|
80
|
+
|
|
81
|
+
send_packet(auth_packet(**@auth.continue(**packet.properties.dup)))
|
|
82
|
+
packet = receive_packet
|
|
83
|
+
end
|
|
84
|
+
packet
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def handle_connack(packet)
|
|
88
|
+
super
|
|
89
|
+
@auth&.success(**packet.properties)
|
|
90
|
+
@qos_send_quota = packet.receive_maximum || max_packet_id
|
|
91
|
+
topic_aliases&.clear_outgoing!(packet)
|
|
92
|
+
self.keep_alive = [@keep_alive, packet.server_keep_alive || @keep_alive].min if packet.server_keep_alive
|
|
93
|
+
log.debug { "Connected: Keep alive: #{@keep_alive}, Send quota: #{@qos_send_quota}" }
|
|
94
|
+
true
|
|
95
|
+
rescue BadAuthenticationMethod, NotAuthorized => e
|
|
96
|
+
@auth&.failed(reason_code: e.code, **packet.properties)
|
|
97
|
+
raise
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def handle_auth(packet)
|
|
101
|
+
raise ProtocolError, 'Unexpected auth packet' unless @auth
|
|
102
|
+
|
|
103
|
+
case packet.reason_code
|
|
104
|
+
when 0x18 # continue
|
|
105
|
+
push_packet(auth_packet(**@auth.continue(**packet.properties.dup)))
|
|
106
|
+
when 0x00 # success - we only see this on reauthenticate, otherwise it comes in connack
|
|
107
|
+
@auth.success(**packet.properties)
|
|
108
|
+
auth_ack(packet)
|
|
109
|
+
else
|
|
110
|
+
raise UnknownReasonCode, packet.reason_code, 'Unknown reason code for auth packet'
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def auth_packet(reason_code: 0x18, **data)
|
|
115
|
+
# always a continuation from the client
|
|
116
|
+
build_packet(:auth, reason_code:, **data)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def handle_publish(packet)
|
|
120
|
+
topic_aliases&.handle_incoming(packet)
|
|
121
|
+
decrement_receive_quota if packet.qos.positive?
|
|
122
|
+
|
|
123
|
+
super
|
|
124
|
+
rescue ReceiveMaximumExceeded, TopicAliasInvalid => e
|
|
125
|
+
push_packet(disconnect_packet(reason_code: e.reason_code))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def handle_puback(packet)
|
|
129
|
+
increment_send_quota if stored_packet?(packet.id)
|
|
130
|
+
super
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def handle_pubrec(packet)
|
|
134
|
+
increment_send_quota if packet.failed? && stored_packet?(packet.id)
|
|
135
|
+
super
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def handle_pubcomp(packet)
|
|
139
|
+
increment_send_quota if stored_packet?(packet.id)
|
|
140
|
+
super
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def handle_disconnect(packet)
|
|
144
|
+
super
|
|
145
|
+
rescue NotAuthorized => e
|
|
146
|
+
@auth&.failed(reason_code: e.code, **packet.properties)
|
|
147
|
+
rescue ServerMoved, UseAnotherServer
|
|
148
|
+
handle_server_redirect(packet)
|
|
149
|
+
raise
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @!visibility private
|
|
153
|
+
def handle_server_redirect(packet)
|
|
154
|
+
# moved temporarily, moved permanently
|
|
155
|
+
# Followed by a UTF-8 Encoded String which can be used by the Client to identify another Server to use.
|
|
156
|
+
# It is a Protocol Error to include the Server Reference more than once.
|
|
157
|
+
# The Server sends DISCONNECT including a Server Reference and Reason Code 0x9C (Use another server) or 0x9D
|
|
158
|
+
# (Server moved) as described in section 4.13.
|
|
159
|
+
socket_factory.redirect(packet.server_redirect) if socket_factory.respond_to?(:redirect)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def decrement_receive_quota
|
|
163
|
+
synchronize do
|
|
164
|
+
raise RecieveMaximumExceeded if @qos_receive_quota.zero?
|
|
165
|
+
|
|
166
|
+
@qos_receive_quota -= 1
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def increment_receive_quota
|
|
171
|
+
synchronize do
|
|
172
|
+
@qos_receive_quota += 1
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def decrement_send_quota
|
|
177
|
+
synchronize do
|
|
178
|
+
@qos_send_condition.wait_until { @qos_send_quota.positive? }
|
|
179
|
+
@qos_send_quota -= 1
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def increment_send_quota
|
|
184
|
+
synchronize do
|
|
185
|
+
@qos_send_quota += 1
|
|
186
|
+
@qos_send_condition.signal
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mqtt/core/client'
|
|
4
|
+
require_relative '../packet'
|
|
5
|
+
|
|
6
|
+
module MQTT
|
|
7
|
+
module V5
|
|
8
|
+
class Client < MQTT::Core::Client
|
|
9
|
+
# Client Session specialisation for MQTT 5.0
|
|
10
|
+
class Session < MQTT::Core::Client::Session
|
|
11
|
+
# If client id was set non-empty, then this is just used
|
|
12
|
+
# If not set, or explicitly empty or explicitly nil, then use server-assigned client id
|
|
13
|
+
def connect_data(**connect)
|
|
14
|
+
check_session_managed_fields(:connect, connect, :clean_start, :session_expiry_interval)
|
|
15
|
+
super.merge!(
|
|
16
|
+
{
|
|
17
|
+
clean_start: clean?,
|
|
18
|
+
session_expiry_interval: session_store.expiry_interval
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# There seems to be no real use for updating the session_expiry_interval on disconnect,
|
|
24
|
+
# and there are potential issues because we don't get an ack to know if the broker ever received it.
|
|
25
|
+
def disconnect_data(**disconnect)
|
|
26
|
+
check_session_managed_fields(:disconnect, disconnect, :session_expiry_interval)
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def connected!(connect, connack)
|
|
31
|
+
session_store.client_id = connack.assigned_client_identifier if connack.assigned_client_identifier
|
|
32
|
+
session_store.expiry_interval = connack.session_expiry_interval if connack.session_expiry_interval
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def qos2_response(response_name, id, exists, **data)
|
|
39
|
+
# Use reason code instead of raise protocol error
|
|
40
|
+
super(response_name, id, true, reason_code: exists ? 0x00 : 0x92, **data)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mqtt/core/client'
|
|
4
|
+
require_relative 'packets'
|
|
5
|
+
require_relative 'client/authenticator'
|
|
6
|
+
require_relative 'client/connection'
|
|
7
|
+
require_relative 'client/session'
|
|
8
|
+
|
|
9
|
+
module MQTT
|
|
10
|
+
module V5
|
|
11
|
+
# An MQTT::V5 Client
|
|
12
|
+
#
|
|
13
|
+
class Client < MQTT::Core::Client
|
|
14
|
+
# @!visibility private
|
|
15
|
+
def self.packet_module
|
|
16
|
+
Packet
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @!visibility private
|
|
20
|
+
def self.protocol_version
|
|
21
|
+
5
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [TopicAlias::Manager] the topic alias manager manages the bi-directional mapping of topic names to
|
|
25
|
+
# topic aliases to limit bandwidth usage.
|
|
26
|
+
attr_reader :topic_aliases
|
|
27
|
+
|
|
28
|
+
# @!visibility private
|
|
29
|
+
def self.new_options(configure_opts)
|
|
30
|
+
{ topic_aliases: configure_opts.delete(:topic_aliases) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @!visibility private
|
|
34
|
+
def initialize(topic_aliases: nil, **)
|
|
35
|
+
@topic_aliases = topic_aliases
|
|
36
|
+
super(**)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# V5 Authentication flow (untested)
|
|
40
|
+
def reauthenticate(**auth)
|
|
41
|
+
connection.reauthenticate(**auth) { |packet| send_and_wait(packet) { |ack| handle_ack(packet, ack) } }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @!method publish(topic, message, **publish)
|
|
45
|
+
# @param [String] topic
|
|
46
|
+
# @param [String] message
|
|
47
|
+
# @param [Hash<Symbol>] publish additional attributes for the `PUBLISH` packet.
|
|
48
|
+
# @option publish [Boolean] topic_alias (true) whether to try to assign a topic alias. See {#topic_aliases}
|
|
49
|
+
#
|
|
50
|
+
# This property is a hint to the {TopicAlias::Manager}. The primary use is to avoid aliasing
|
|
51
|
+
# a topic that will only be used once, or to indicate that this is the last publish to a topic, and thus
|
|
52
|
+
# leave room in the alias cache for other topics.
|
|
53
|
+
#
|
|
54
|
+
# If you pass an Integer, it is treated as `true` if positive otherwise `false`.
|
|
55
|
+
# The value is not used directly.
|
|
56
|
+
# @return [self]
|
|
57
|
+
# @see Packet::Publish
|
|
58
|
+
# @see Core::Client#publish
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MQTT
|
|
4
|
+
module V5
|
|
5
|
+
# V5 response error - with reason code
|
|
6
|
+
class ResponseError < MQTT::ResponseError
|
|
7
|
+
class << self
|
|
8
|
+
attr_reader :code
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(message = nil)
|
|
12
|
+
code = self.class.code || 0xff
|
|
13
|
+
super(format '(0x%02<code>x) %<message>s', code:, message:)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class UnknownReasonCode < ResponseError; end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../packet'
|
|
4
|
+
module MQTT
|
|
5
|
+
module V5
|
|
6
|
+
module Packet
|
|
7
|
+
# MQTT 5.0 AUTH packet
|
|
8
|
+
#
|
|
9
|
+
# Sent by client or broker for extended authentication exchange.
|
|
10
|
+
#
|
|
11
|
+
# @see https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901217 MQTT 5.0 Spec §3.15
|
|
12
|
+
class Auth
|
|
13
|
+
include Packet
|
|
14
|
+
|
|
15
|
+
fixed(15)
|
|
16
|
+
|
|
17
|
+
# @!parse include ReasonCodeAck
|
|
18
|
+
# @!attribute [r] reason_code
|
|
19
|
+
# authentication status
|
|
20
|
+
#
|
|
21
|
+
# ✅ Success:
|
|
22
|
+
#
|
|
23
|
+
# - `0x00` Success
|
|
24
|
+
# - `0x18` Continue authentication
|
|
25
|
+
# - `0x19` Re-authenticate
|
|
26
|
+
#
|
|
27
|
+
# @return [ReasonCode]
|
|
28
|
+
|
|
29
|
+
# @!group Properties
|
|
30
|
+
|
|
31
|
+
# @!attribute [r] authentication_method
|
|
32
|
+
# @return [String<UTF8>] authentication method name
|
|
33
|
+
# @!attribute [r] authentication_data
|
|
34
|
+
# @return [String<Binary>] authentication data
|
|
35
|
+
# @!attribute [r] reason_string
|
|
36
|
+
# @return [String<UTF8>] human-readable reason for the response
|
|
37
|
+
# @!attribute [r] user_properties
|
|
38
|
+
# @return [Array<String, String>] user-defined properties as key-value pairs
|
|
39
|
+
|
|
40
|
+
# @!endgroup
|
|
41
|
+
|
|
42
|
+
variable(
|
|
43
|
+
reason_code:, # automatically includes ReasonCodeAck
|
|
44
|
+
properties:
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# @return [self] if reason code is 0x18 (Continue authentication)
|
|
48
|
+
# @raise [ProtocolError] if reason code is not 0x18
|
|
49
|
+
def continue!
|
|
50
|
+
return self if reason_code == 0x18
|
|
51
|
+
|
|
52
|
+
raise ProtocolError, reason_code, 'expected reason code 0x18 (auth continue)'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../packet'
|
|
4
|
+
|
|
5
|
+
module MQTT
|
|
6
|
+
module V5
|
|
7
|
+
module Packet
|
|
8
|
+
# MQTT 5.0 CONNACK packet
|
|
9
|
+
#
|
|
10
|
+
# Connection acknowledgement sent by broker in response to a CONNECT packet.
|
|
11
|
+
#
|
|
12
|
+
# @see Core::Client#connect
|
|
13
|
+
# @see https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901074 MQTT 5.0 Spec §3.2
|
|
14
|
+
class Connack
|
|
15
|
+
include Packet
|
|
16
|
+
|
|
17
|
+
fixed(2)
|
|
18
|
+
|
|
19
|
+
# @!attribute [r] session_present
|
|
20
|
+
# @return [Boolean] whether the broker has a stored session for this client
|
|
21
|
+
|
|
22
|
+
# @!parse include ReasonCodeAck
|
|
23
|
+
# @!attribute [r] reason_code
|
|
24
|
+
# connection acknowledgement status
|
|
25
|
+
#
|
|
26
|
+
# ✅ Success:
|
|
27
|
+
#
|
|
28
|
+
# - `0x00` Success
|
|
29
|
+
#
|
|
30
|
+
# ❌ Error:
|
|
31
|
+
#
|
|
32
|
+
# - `0x80` Unspecified error
|
|
33
|
+
# - `0x81` Malformed Packet
|
|
34
|
+
# - `0x82` Protocol Error
|
|
35
|
+
# - `0x83` Implementation specific error
|
|
36
|
+
# - `0x84` Unsupported Protocol Version
|
|
37
|
+
# - `0x85` Client Identifier not valid
|
|
38
|
+
# - `0x86` Bad User Name or Password
|
|
39
|
+
# - `0x87` Not authorized
|
|
40
|
+
# - `0x88` Server unavailable
|
|
41
|
+
# - `0x89` Server busy
|
|
42
|
+
# - `0x8A` Banned
|
|
43
|
+
# - `0x8C` Bad authentication method
|
|
44
|
+
# - `0x90` Topic Name invalid
|
|
45
|
+
# - `0x95` Packet too large
|
|
46
|
+
# - `0x97` Quota exceeded
|
|
47
|
+
# - `0x99` Payload format invalid
|
|
48
|
+
# - `0x9A` Retain not supported
|
|
49
|
+
# - `0x9B` QoS not supported
|
|
50
|
+
# - `0x9C` Use another server
|
|
51
|
+
# - `0x9F` Connection rate exceeded
|
|
52
|
+
#
|
|
53
|
+
# @return [ReasonCode]
|
|
54
|
+
|
|
55
|
+
# @!group Properties
|
|
56
|
+
|
|
57
|
+
# @!attribute [r] session_expiry_interval
|
|
58
|
+
# @return [Integer] session expiry interval in seconds
|
|
59
|
+
# @!attribute [r] receive_maximum
|
|
60
|
+
# @return [Integer] maximum number of QoS 1 and 2 messages the server will process concurrently
|
|
61
|
+
# @!attribute [r] maximum_qos
|
|
62
|
+
# @return [Integer] maximum QoS level supported by the server
|
|
63
|
+
# @!attribute [r] retain_available
|
|
64
|
+
# @return [Boolean] whether the server supports retained messages
|
|
65
|
+
# @!attribute [r] maximum_packet_size
|
|
66
|
+
# @return [Integer] maximum packet size the server is willing to accept
|
|
67
|
+
# @!attribute [r] assigned_client_identifier
|
|
68
|
+
# @return [String<UTF8>] client identifier assigned by the server
|
|
69
|
+
# @!attribute [r] topic_alias_maximum
|
|
70
|
+
# @return [Integer] maximum topic alias value supported by the server
|
|
71
|
+
# @!attribute [r] reason_string
|
|
72
|
+
# @return [String<UTF8>] human-readable reason for the response
|
|
73
|
+
# @!attribute [r] user_properties
|
|
74
|
+
# @return [Array<String, String>] user-defined properties as key-value pairs
|
|
75
|
+
# @!attribute [r] wildcard_subscription_available
|
|
76
|
+
# @return [Boolean] whether the server supports wildcard subscriptions
|
|
77
|
+
# @!attribute [r] subscription_identifier_available
|
|
78
|
+
# @return [Boolean] whether the server supports subscription identifiers
|
|
79
|
+
# @!attribute [r] shared_subscription_available
|
|
80
|
+
# @return [Boolean] whether the server supports shared subscriptions
|
|
81
|
+
# @!attribute [r] server_keep_alive
|
|
82
|
+
# @return [Integer] keep alive time in seconds assigned by the server
|
|
83
|
+
# @!attribute [r] response_information
|
|
84
|
+
# @return [String<UTF8>] response information for request/response
|
|
85
|
+
# @!attribute [r] server_reference
|
|
86
|
+
# @return [String<UTF8>] server reference for redirection
|
|
87
|
+
# @!attribute [r] authentication_method
|
|
88
|
+
# @return [String<UTF8>] authentication method name
|
|
89
|
+
# @!attribute [r] authentication_data
|
|
90
|
+
# @return [String<Binary>] authentication data
|
|
91
|
+
|
|
92
|
+
# @!endgroup
|
|
93
|
+
|
|
94
|
+
variable(
|
|
95
|
+
flags: flags([:reserved, 7], [:session_present, 1]),
|
|
96
|
+
reason_code:, # automatically includes ReasonCodeAck
|
|
97
|
+
properties:
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
alias connect_reason_code reason_code # align with spec naming
|
|
101
|
+
alias session_present? session_present
|
|
102
|
+
|
|
103
|
+
# @!visibility private
|
|
104
|
+
def defaults
|
|
105
|
+
super.merge!(session_present: false)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|