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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/lib/mqtt/v5/async/client.rb +27 -0
  3. data/lib/mqtt/v5/client/authenticator.rb +83 -0
  4. data/lib/mqtt/v5/client/connection.rb +192 -0
  5. data/lib/mqtt/v5/client/session.rb +45 -0
  6. data/lib/mqtt/v5/client.rb +61 -0
  7. data/lib/mqtt/v5/errors.rb +19 -0
  8. data/lib/mqtt/v5/packet/auth.rb +57 -0
  9. data/lib/mqtt/v5/packet/connack.rb +110 -0
  10. data/lib/mqtt/v5/packet/connect.rb +130 -0
  11. data/lib/mqtt/v5/packet/disconnect.rb +78 -0
  12. data/lib/mqtt/v5/packet/ping_req.rb +20 -0
  13. data/lib/mqtt/v5/packet/ping_resp.rb +20 -0
  14. data/lib/mqtt/v5/packet/pub_ack.rb +60 -0
  15. data/lib/mqtt/v5/packet/pub_comp.rb +53 -0
  16. data/lib/mqtt/v5/packet/pub_rec.rb +60 -0
  17. data/lib/mqtt/v5/packet/pub_rel.rb +53 -0
  18. data/lib/mqtt/v5/packet/publish.rb +122 -0
  19. data/lib/mqtt/v5/packet/reason_code.rb +112 -0
  20. data/lib/mqtt/v5/packet/sub_ack.rb +66 -0
  21. data/lib/mqtt/v5/packet/subscribe.rb +87 -0
  22. data/lib/mqtt/v5/packet/unsub_ack.rb +59 -0
  23. data/lib/mqtt/v5/packet/unsubscribe.rb +112 -0
  24. data/lib/mqtt/v5/packet.rb +147 -0
  25. data/lib/mqtt/v5/packets.rb +25 -0
  26. data/lib/mqtt/v5/topic_alias/cache.rb +87 -0
  27. data/lib/mqtt/v5/topic_alias/frequency_weighted_policy.rb +50 -0
  28. data/lib/mqtt/v5/topic_alias/length_weighted_policy.rb +20 -0
  29. data/lib/mqtt/v5/topic_alias/lru_policy.rb +36 -0
  30. data/lib/mqtt/v5/topic_alias/manager.rb +143 -0
  31. data/lib/mqtt/v5/topic_alias/policy.rb +34 -0
  32. data/lib/mqtt/v5/topic_alias/weighted_policy.rb +48 -0
  33. data/lib/mqtt/v5/topic_alias.rb +27 -0
  34. data/lib/mqtt/v5/version.rb +11 -0
  35. data/lib/mqtt/v5.rb +4 -0
  36. 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