@annadata/capacitor-mqtt-quic 0.1.0
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.
- package/README.md +399 -0
- package/android/NGTCP2_BUILD_INSTRUCTIONS.md +319 -0
- package/android/build-nghttp3.sh +182 -0
- package/android/build-ngtcp2.sh +289 -0
- package/android/build-openssl.sh +302 -0
- package/android/build.gradle +75 -0
- package/android/gradle.properties +3 -0
- package/android/proguard-rules.pro +2 -0
- package/android/settings.gradle +1 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/assets/mqttquic_ca.pem +5 -0
- package/android/src/main/cpp/CMakeLists.txt +157 -0
- package/android/src/main/cpp/ngtcp2_jni.cpp +928 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/MqttQuicPlugin.kt +232 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/client/MQTTClient.kt +339 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5Properties.kt +250 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5Protocol.kt +281 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5ReasonCodes.kt +109 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocol.kt +249 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTTypes.kt +47 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/quic/NGTCP2Client.kt +184 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicClientStub.kt +54 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicTypes.kt +21 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/transport/QUICStreamAdapter.kt +37 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/transport/StreamTransport.kt +91 -0
- package/android/src/test/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocolTest.kt +92 -0
- package/dist/esm/definitions.d.ts +66 -0
- package/dist/esm/definitions.d.ts.map +1 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +28 -0
- package/dist/esm/web.d.ts.map +1 -0
- package/dist/esm/web.js +183 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +217 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +215 -0
- package/dist/plugin.js.map +1 -0
- package/ios/MqttQuicPlugin.podspec +34 -0
- package/ios/NGTCP2_BUILD_INSTRUCTIONS.md +302 -0
- package/ios/Sources/MqttQuicPlugin/Client/MQTTClient.swift +343 -0
- package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5Properties.swift +280 -0
- package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5Protocol.swift +333 -0
- package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5ReasonCodes.swift +113 -0
- package/ios/Sources/MqttQuicPlugin/MQTT/MQTTProtocol.swift +322 -0
- package/ios/Sources/MqttQuicPlugin/MQTT/MQTTTypes.swift +54 -0
- package/ios/Sources/MqttQuicPlugin/MqttQuicPlugin.swift +229 -0
- package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Bridge.h +29 -0
- package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Bridge.mm +865 -0
- package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Client.swift +262 -0
- package/ios/Sources/MqttQuicPlugin/QUIC/QuicClientStub.swift +66 -0
- package/ios/Sources/MqttQuicPlugin/QUIC/QuicTypes.swift +23 -0
- package/ios/Sources/MqttQuicPlugin/Resources/mqttquic_ca.pem +5 -0
- package/ios/Sources/MqttQuicPlugin/Transport/QUICStreamAdapter.swift +50 -0
- package/ios/Sources/MqttQuicPlugin/Transport/StreamTransport.swift +105 -0
- package/ios/Tests/MQTTProtocolTests.swift +82 -0
- package/ios/build-nghttp3.sh +173 -0
- package/ios/build-ngtcp2.sh +278 -0
- package/ios/build-openssl.sh +405 -0
- package/package.json +63 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
package ai.annadata.mqttquic.mqtt
|
|
2
|
+
|
|
3
|
+
import java.nio.charset.StandardCharsets
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* MQTT 5.0 Property Types. Matches MQTTD mqttd/properties.py.
|
|
7
|
+
*/
|
|
8
|
+
object MQTT5PropertyType {
|
|
9
|
+
const val PAYLOAD_FORMAT_INDICATOR: Byte = 0x01
|
|
10
|
+
const val MESSAGE_EXPIRY_INTERVAL: Byte = 0x02
|
|
11
|
+
const val CONTENT_TYPE: Byte = 0x03
|
|
12
|
+
const val RESPONSE_TOPIC: Byte = 0x08
|
|
13
|
+
const val CORRELATION_DATA: Byte = 0x09
|
|
14
|
+
const val SUBSCRIPTION_IDENTIFIER: Byte = 0x0B
|
|
15
|
+
const val SESSION_EXPIRY_INTERVAL: Byte = 0x11
|
|
16
|
+
const val ASSIGNED_CLIENT_IDENTIFIER: Byte = 0x12
|
|
17
|
+
const val SERVER_KEEP_ALIVE: Byte = 0x13
|
|
18
|
+
const val AUTHENTICATION_METHOD: Byte = 0x15
|
|
19
|
+
const val AUTHENTICATION_DATA: Byte = 0x16
|
|
20
|
+
const val REQUEST_PROBLEM_INFORMATION: Byte = 0x17
|
|
21
|
+
const val WILL_DELAY_INTERVAL: Byte = 0x18
|
|
22
|
+
const val REQUEST_RESPONSE_INFORMATION: Byte = 0x19
|
|
23
|
+
const val RESPONSE_INFORMATION: Byte = 0x1A
|
|
24
|
+
const val SERVER_REFERENCE: Byte = 0x1C
|
|
25
|
+
const val REASON_STRING: Byte = 0x1F
|
|
26
|
+
const val RECEIVE_MAXIMUM: Byte = 0x21
|
|
27
|
+
const val TOPIC_ALIAS_MAXIMUM: Byte = 0x22
|
|
28
|
+
const val TOPIC_ALIAS: Byte = 0x23
|
|
29
|
+
const val MAXIMUM_QOS: Byte = 0x24
|
|
30
|
+
const val RETAIN_AVAILABLE: Byte = 0x25
|
|
31
|
+
const val USER_PROPERTY: Byte = 0x26
|
|
32
|
+
const val MAXIMUM_PACKET_SIZE: Byte = 0x27
|
|
33
|
+
const val WILDCARD_SUBSCRIPTION_AVAILABLE: Byte = 0x28
|
|
34
|
+
const val SUBSCRIPTION_IDENTIFIER_AVAILABLE: Byte = 0x29
|
|
35
|
+
const val SHARED_SUBSCRIPTION_AVAILABLE: Byte = 0x2A
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* MQTT 5.0 Properties encoder/decoder. Matches MQTTD mqttd/properties.py.
|
|
40
|
+
*/
|
|
41
|
+
object MQTT5PropertyEncoder {
|
|
42
|
+
|
|
43
|
+
fun encodeProperties(props: Map<Int, Any>): ByteArray {
|
|
44
|
+
val result = mutableListOf<Byte>()
|
|
45
|
+
val sorted = props.toList().sortedBy { it.first }
|
|
46
|
+
|
|
47
|
+
for ((propId, value) in sorted) {
|
|
48
|
+
// Handle subscription identifier list
|
|
49
|
+
if (propId == MQTT5PropertyType.SUBSCRIPTION_IDENTIFIER.toInt() && value is List<*>) {
|
|
50
|
+
for (subId in value) {
|
|
51
|
+
result.add(propId.toByte())
|
|
52
|
+
result.addAll(encodeVariableByteInteger((subId as? Int) ?: 0).toList())
|
|
53
|
+
}
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
result.add(propId.toByte())
|
|
58
|
+
|
|
59
|
+
when (propId) {
|
|
60
|
+
MQTT5PropertyType.PAYLOAD_FORMAT_INDICATOR.toInt() -> {
|
|
61
|
+
result.add(((value as? Int) ?: 0).toByte())
|
|
62
|
+
}
|
|
63
|
+
MQTT5PropertyType.MESSAGE_EXPIRY_INTERVAL.toInt(),
|
|
64
|
+
MQTT5PropertyType.SESSION_EXPIRY_INTERVAL.toInt(),
|
|
65
|
+
MQTT5PropertyType.WILL_DELAY_INTERVAL.toInt(),
|
|
66
|
+
MQTT5PropertyType.MAXIMUM_PACKET_SIZE.toInt() -> {
|
|
67
|
+
val v = ((value as? Long) ?: 0L).toInt()
|
|
68
|
+
result.add((v shr 24).toByte())
|
|
69
|
+
result.add((v shr 16).toByte())
|
|
70
|
+
result.add((v shr 8).toByte())
|
|
71
|
+
result.add(v.toByte())
|
|
72
|
+
}
|
|
73
|
+
MQTT5PropertyType.CONTENT_TYPE.toInt(),
|
|
74
|
+
MQTT5PropertyType.RESPONSE_TOPIC.toInt(),
|
|
75
|
+
MQTT5PropertyType.ASSIGNED_CLIENT_IDENTIFIER.toInt(),
|
|
76
|
+
MQTT5PropertyType.AUTHENTICATION_METHOD.toInt(),
|
|
77
|
+
MQTT5PropertyType.RESPONSE_INFORMATION.toInt(),
|
|
78
|
+
MQTT5PropertyType.SERVER_REFERENCE.toInt(),
|
|
79
|
+
MQTT5PropertyType.REASON_STRING.toInt() -> {
|
|
80
|
+
result.addAll(encodeString((value as? String) ?: "").toList())
|
|
81
|
+
}
|
|
82
|
+
MQTT5PropertyType.CORRELATION_DATA.toInt(),
|
|
83
|
+
MQTT5PropertyType.AUTHENTICATION_DATA.toInt() -> {
|
|
84
|
+
val data = (value as? ByteArray) ?: ByteArray(0)
|
|
85
|
+
result.add((data.size shr 8).toByte())
|
|
86
|
+
result.add((data.size and 0xFF).toByte())
|
|
87
|
+
result.addAll(data.toList())
|
|
88
|
+
}
|
|
89
|
+
MQTT5PropertyType.SUBSCRIPTION_IDENTIFIER.toInt() -> {
|
|
90
|
+
result.addAll(encodeVariableByteInteger((value as? Int) ?: 0).toList())
|
|
91
|
+
}
|
|
92
|
+
MQTT5PropertyType.SERVER_KEEP_ALIVE.toInt(),
|
|
93
|
+
MQTT5PropertyType.RECEIVE_MAXIMUM.toInt(),
|
|
94
|
+
MQTT5PropertyType.TOPIC_ALIAS_MAXIMUM.toInt(),
|
|
95
|
+
MQTT5PropertyType.TOPIC_ALIAS.toInt() -> {
|
|
96
|
+
val v = ((value as? Int) ?: 0).toInt()
|
|
97
|
+
result.add((v shr 8).toByte())
|
|
98
|
+
result.add((v and 0xFF).toByte())
|
|
99
|
+
}
|
|
100
|
+
MQTT5PropertyType.MAXIMUM_QOS.toInt(),
|
|
101
|
+
MQTT5PropertyType.RETAIN_AVAILABLE.toInt(),
|
|
102
|
+
MQTT5PropertyType.REQUEST_PROBLEM_INFORMATION.toInt(),
|
|
103
|
+
MQTT5PropertyType.REQUEST_RESPONSE_INFORMATION.toInt(),
|
|
104
|
+
MQTT5PropertyType.WILDCARD_SUBSCRIPTION_AVAILABLE.toInt(),
|
|
105
|
+
MQTT5PropertyType.SUBSCRIPTION_IDENTIFIER_AVAILABLE.toInt(),
|
|
106
|
+
MQTT5PropertyType.SHARED_SUBSCRIPTION_AVAILABLE.toInt() -> {
|
|
107
|
+
result.add(((value as? Int) ?: 0).toByte())
|
|
108
|
+
}
|
|
109
|
+
MQTT5PropertyType.USER_PROPERTY.toInt() -> {
|
|
110
|
+
if (value is Pair<*, *>) {
|
|
111
|
+
result.addAll(encodeString((value.first as? String) ?: "").toList())
|
|
112
|
+
result.addAll(encodeString((value.second as? String) ?: "").toList())
|
|
113
|
+
} else {
|
|
114
|
+
throw IllegalArgumentException("USER_PROPERTY must be Pair<String, String>")
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else -> throw IllegalArgumentException("Unknown property type: $propId")
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return result.toByteArray()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fun decodeProperties(data: ByteArray, offset: Int = 0): Pair<Map<Int, Any>, Int> {
|
|
125
|
+
val props = mutableMapOf<Int, Any>()
|
|
126
|
+
var pos = offset
|
|
127
|
+
|
|
128
|
+
while (pos < data.size) {
|
|
129
|
+
val propId = data[pos].toInt() and 0xFF
|
|
130
|
+
pos++
|
|
131
|
+
|
|
132
|
+
when (propId) {
|
|
133
|
+
MQTT5PropertyType.PAYLOAD_FORMAT_INDICATOR.toInt() -> {
|
|
134
|
+
props[propId] = data[pos].toInt() and 0xFF
|
|
135
|
+
pos++
|
|
136
|
+
}
|
|
137
|
+
MQTT5PropertyType.MESSAGE_EXPIRY_INTERVAL.toInt(),
|
|
138
|
+
MQTT5PropertyType.SESSION_EXPIRY_INTERVAL.toInt(),
|
|
139
|
+
MQTT5PropertyType.WILL_DELAY_INTERVAL.toInt(),
|
|
140
|
+
MQTT5PropertyType.MAXIMUM_PACKET_SIZE.toInt() -> {
|
|
141
|
+
val v = ((data[pos].toInt() and 0xFF) shl 24) or
|
|
142
|
+
((data[pos + 1].toInt() and 0xFF) shl 16) or
|
|
143
|
+
((data[pos + 2].toInt() and 0xFF) shl 8) or
|
|
144
|
+
(data[pos + 3].toInt() and 0xFF)
|
|
145
|
+
props[propId] = v
|
|
146
|
+
pos += 4
|
|
147
|
+
}
|
|
148
|
+
MQTT5PropertyType.CONTENT_TYPE.toInt(),
|
|
149
|
+
MQTT5PropertyType.RESPONSE_TOPIC.toInt(),
|
|
150
|
+
MQTT5PropertyType.ASSIGNED_CLIENT_IDENTIFIER.toInt(),
|
|
151
|
+
MQTT5PropertyType.AUTHENTICATION_METHOD.toInt(),
|
|
152
|
+
MQTT5PropertyType.RESPONSE_INFORMATION.toInt(),
|
|
153
|
+
MQTT5PropertyType.SERVER_REFERENCE.toInt(),
|
|
154
|
+
MQTT5PropertyType.REASON_STRING.toInt() -> {
|
|
155
|
+
val (s, next) = decodeString(data, pos)
|
|
156
|
+
props[propId] = s
|
|
157
|
+
pos = next
|
|
158
|
+
}
|
|
159
|
+
MQTT5PropertyType.CORRELATION_DATA.toInt(),
|
|
160
|
+
MQTT5PropertyType.AUTHENTICATION_DATA.toInt() -> {
|
|
161
|
+
val len = ((data[pos].toInt() and 0xFF) shl 8) or (data[pos + 1].toInt() and 0xFF)
|
|
162
|
+
pos += 2
|
|
163
|
+
props[propId] = data.copyOfRange(pos, pos + len)
|
|
164
|
+
pos += len
|
|
165
|
+
}
|
|
166
|
+
MQTT5PropertyType.SUBSCRIPTION_IDENTIFIER.toInt() -> {
|
|
167
|
+
val (v, consumed) = decodeVariableByteInteger(data, pos)
|
|
168
|
+
val list = (props[propId] as? MutableList<Int>) ?: mutableListOf()
|
|
169
|
+
list.add(v)
|
|
170
|
+
props[propId] = list
|
|
171
|
+
pos += consumed
|
|
172
|
+
}
|
|
173
|
+
MQTT5PropertyType.SERVER_KEEP_ALIVE.toInt(),
|
|
174
|
+
MQTT5PropertyType.RECEIVE_MAXIMUM.toInt(),
|
|
175
|
+
MQTT5PropertyType.TOPIC_ALIAS_MAXIMUM.toInt(),
|
|
176
|
+
MQTT5PropertyType.TOPIC_ALIAS.toInt() -> {
|
|
177
|
+
val v = ((data[pos].toInt() and 0xFF) shl 8) or (data[pos + 1].toInt() and 0xFF)
|
|
178
|
+
props[propId] = v
|
|
179
|
+
pos += 2
|
|
180
|
+
}
|
|
181
|
+
MQTT5PropertyType.MAXIMUM_QOS.toInt(),
|
|
182
|
+
MQTT5PropertyType.RETAIN_AVAILABLE.toInt(),
|
|
183
|
+
MQTT5PropertyType.REQUEST_PROBLEM_INFORMATION.toInt(),
|
|
184
|
+
MQTT5PropertyType.REQUEST_RESPONSE_INFORMATION.toInt(),
|
|
185
|
+
MQTT5PropertyType.WILDCARD_SUBSCRIPTION_AVAILABLE.toInt(),
|
|
186
|
+
MQTT5PropertyType.SUBSCRIPTION_IDENTIFIER_AVAILABLE.toInt(),
|
|
187
|
+
MQTT5PropertyType.SHARED_SUBSCRIPTION_AVAILABLE.toInt() -> {
|
|
188
|
+
props[propId] = data[pos].toInt() and 0xFF
|
|
189
|
+
pos++
|
|
190
|
+
}
|
|
191
|
+
MQTT5PropertyType.USER_PROPERTY.toInt() -> {
|
|
192
|
+
val (name, next1) = decodeString(data, pos)
|
|
193
|
+
pos = next1
|
|
194
|
+
val (value, next2) = decodeString(data, pos)
|
|
195
|
+
pos = next2
|
|
196
|
+
val list = (props[propId] as? MutableList<Pair<String, String>>) ?: mutableListOf()
|
|
197
|
+
list.add(name to value)
|
|
198
|
+
props[propId] = list
|
|
199
|
+
}
|
|
200
|
+
else -> break // Unknown property - skip
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return props to (pos - offset)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private fun encodeString(s: String): ByteArray {
|
|
208
|
+
val utf8 = s.toByteArray(StandardCharsets.UTF_8)
|
|
209
|
+
if (utf8.size > 0xFFFF) throw IllegalArgumentException("String too long")
|
|
210
|
+
return byteArrayOf((utf8.size shr 8).toByte(), (utf8.size and 0xFF).toByte()) + utf8
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private fun decodeString(data: ByteArray, offset: Int): Pair<String, Int> {
|
|
214
|
+
if (offset + 2 > data.size) throw IllegalArgumentException("Insufficient data for string length")
|
|
215
|
+
val len = ((data[offset].toInt() and 0xFF) shl 8) or (data[offset + 1].toInt() and 0xFF)
|
|
216
|
+
val start = offset + 2
|
|
217
|
+
if (start + len > data.size) throw IllegalArgumentException("Insufficient data for string content")
|
|
218
|
+
val sub = data.copyOfRange(start, start + len)
|
|
219
|
+
val s = String(sub, StandardCharsets.UTF_8)
|
|
220
|
+
return s to (start + len)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private fun encodeVariableByteInteger(value: Int): ByteArray {
|
|
224
|
+
if (value < 0 || value > 268_435_455) throw IllegalArgumentException("Invalid variable byte integer: $value")
|
|
225
|
+
val enc = mutableListOf<Byte>()
|
|
226
|
+
var n = value
|
|
227
|
+
do {
|
|
228
|
+
var b = (n % 128).toByte()
|
|
229
|
+
n /= 128
|
|
230
|
+
if (n > 0) b = (b.toInt() or 0x80).toByte()
|
|
231
|
+
enc.add(b)
|
|
232
|
+
} while (n > 0)
|
|
233
|
+
return enc.toByteArray()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private fun decodeVariableByteInteger(data: ByteArray, offset: Int): Pair<Int, Int> {
|
|
237
|
+
var mul = 1
|
|
238
|
+
var value = 0
|
|
239
|
+
var i = offset
|
|
240
|
+
repeat(4) {
|
|
241
|
+
if (i >= data.size) throw IllegalArgumentException("Insufficient data for variable byte integer")
|
|
242
|
+
val b = data[i].toInt() and 0xFF
|
|
243
|
+
value += (b and 0x7F) * mul
|
|
244
|
+
i++
|
|
245
|
+
if ((b and 0x80) == 0) return@repeat
|
|
246
|
+
mul *= 128
|
|
247
|
+
}
|
|
248
|
+
return value to (i - offset)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
package ai.annadata.mqttquic.mqtt
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MQTT 5.0 Protocol implementation. Matches MQTTD mqttd/protocol_v5.py.
|
|
5
|
+
*/
|
|
6
|
+
object MQTT5Protocol {
|
|
7
|
+
|
|
8
|
+
fun buildConnectV5(
|
|
9
|
+
clientId: String,
|
|
10
|
+
username: String? = null,
|
|
11
|
+
password: String? = null,
|
|
12
|
+
keepalive: Int = 60,
|
|
13
|
+
cleanStart: Boolean = true,
|
|
14
|
+
sessionExpiryInterval: Int? = null,
|
|
15
|
+
receiveMaximum: Int? = null,
|
|
16
|
+
maximumPacketSize: Int? = null,
|
|
17
|
+
topicAliasMaximum: Int? = null,
|
|
18
|
+
requestResponseInformation: Int? = null,
|
|
19
|
+
requestProblemInformation: Int? = null,
|
|
20
|
+
authenticationMethod: String? = null,
|
|
21
|
+
authenticationData: ByteArray? = null,
|
|
22
|
+
properties: Map<Int, Any>? = null
|
|
23
|
+
): ByteArray {
|
|
24
|
+
val variableHeader = mutableListOf<Byte>()
|
|
25
|
+
variableHeader.addAll(MQTTProtocol.encodeString("MQTT").toList())
|
|
26
|
+
variableHeader.add(MQTTProtocolLevel.V5)
|
|
27
|
+
|
|
28
|
+
var flags = 0
|
|
29
|
+
if (cleanStart) flags = flags or 0x02
|
|
30
|
+
if (username != null) flags = flags or MQTTConnectFlags.USERNAME
|
|
31
|
+
if (password != null) flags = flags or MQTTConnectFlags.PASSWORD
|
|
32
|
+
variableHeader.add(flags.toByte())
|
|
33
|
+
variableHeader.add((keepalive shr 8).toByte())
|
|
34
|
+
variableHeader.add((keepalive and 0xFF).toByte())
|
|
35
|
+
|
|
36
|
+
val connectProps = mutableMapOf<Int, Any>()
|
|
37
|
+
sessionExpiryInterval?.let { connectProps[MQTT5PropertyType.SESSION_EXPIRY_INTERVAL.toInt()] = it }
|
|
38
|
+
receiveMaximum?.let { connectProps[MQTT5PropertyType.RECEIVE_MAXIMUM.toInt()] = it }
|
|
39
|
+
maximumPacketSize?.let { connectProps[MQTT5PropertyType.MAXIMUM_PACKET_SIZE.toInt()] = it }
|
|
40
|
+
topicAliasMaximum?.let { connectProps[MQTT5PropertyType.TOPIC_ALIAS_MAXIMUM.toInt()] = it }
|
|
41
|
+
requestResponseInformation?.let { connectProps[MQTT5PropertyType.REQUEST_RESPONSE_INFORMATION.toInt()] = it }
|
|
42
|
+
requestProblemInformation?.let { connectProps[MQTT5PropertyType.REQUEST_PROBLEM_INFORMATION.toInt()] = it }
|
|
43
|
+
authenticationMethod?.let { connectProps[MQTT5PropertyType.AUTHENTICATION_METHOD.toInt()] = it }
|
|
44
|
+
authenticationData?.let { connectProps[MQTT5PropertyType.AUTHENTICATION_DATA.toInt()] = it }
|
|
45
|
+
properties?.let { connectProps.putAll(it) }
|
|
46
|
+
|
|
47
|
+
val propsBytes = MQTT5PropertyEncoder.encodeProperties(connectProps)
|
|
48
|
+
val propsLen = MQTTProtocol.encodeRemainingLength(propsBytes.size)
|
|
49
|
+
variableHeader.addAll(propsLen.toList())
|
|
50
|
+
variableHeader.addAll(propsBytes.toList())
|
|
51
|
+
|
|
52
|
+
val payload = mutableListOf<Byte>()
|
|
53
|
+
payload.addAll(MQTTProtocol.encodeString(clientId).toList())
|
|
54
|
+
payload.add(0x00) // Will Properties length = 0
|
|
55
|
+
username?.let { payload.addAll(MQTTProtocol.encodeString(it).toList()) }
|
|
56
|
+
password?.let { payload.addAll(MQTTProtocol.encodeString(it).toList()) }
|
|
57
|
+
|
|
58
|
+
val remLen = variableHeader.size + payload.size
|
|
59
|
+
val fixed = mutableListOf<Byte>()
|
|
60
|
+
fixed.add(MQTTMessageType.CONNECT)
|
|
61
|
+
fixed.addAll(MQTTProtocol.encodeRemainingLength(remLen).toList())
|
|
62
|
+
|
|
63
|
+
return (fixed + variableHeader + payload).toByteArray()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fun buildConnackV5(
|
|
67
|
+
reasonCode: Int = MQTT5ReasonCode.SUCCESS,
|
|
68
|
+
sessionPresent: Boolean = false,
|
|
69
|
+
properties: Map<Int, Any>? = null
|
|
70
|
+
): ByteArray {
|
|
71
|
+
val variableHeader = mutableListOf<Byte>()
|
|
72
|
+
variableHeader.add(if (sessionPresent) 0x01 else 0x00)
|
|
73
|
+
variableHeader.add(reasonCode.toByte())
|
|
74
|
+
|
|
75
|
+
val props = properties ?: emptyMap()
|
|
76
|
+
val propsBytes = MQTT5PropertyEncoder.encodeProperties(props)
|
|
77
|
+
val propsLen = MQTTProtocol.encodeRemainingLength(propsBytes.size)
|
|
78
|
+
variableHeader.addAll(propsLen.toList())
|
|
79
|
+
variableHeader.addAll(propsBytes.toList())
|
|
80
|
+
|
|
81
|
+
val remLen = variableHeader.size
|
|
82
|
+
val fixed = mutableListOf<Byte>()
|
|
83
|
+
fixed.add(MQTTMessageType.CONNACK)
|
|
84
|
+
fixed.addAll(MQTTProtocol.encodeRemainingLength(remLen).toList())
|
|
85
|
+
|
|
86
|
+
return (fixed + variableHeader).toByteArray()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fun parseConnackV5(data: ByteArray, offset: Int = 0): Triple<Boolean, Int, Map<Int, Any>> {
|
|
90
|
+
if (offset + 2 > data.size) throw IllegalArgumentException("Insufficient data for CONNACK")
|
|
91
|
+
val sessionPresent = (data[offset].toInt() and 0x01) != 0
|
|
92
|
+
val reasonCode = data[offset + 1].toInt() and 0xFF
|
|
93
|
+
var pos = offset + 2
|
|
94
|
+
|
|
95
|
+
val (propLen, propLenBytes) = MQTTProtocol.decodeRemainingLength(data, pos)
|
|
96
|
+
pos += propLenBytes
|
|
97
|
+
val (props, _) = MQTT5PropertyEncoder.decodeProperties(data.copyOfRange(pos, pos + propLen), 0)
|
|
98
|
+
pos += propLen
|
|
99
|
+
|
|
100
|
+
return Triple(sessionPresent, reasonCode, props)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fun buildPublishV5(
|
|
104
|
+
topic: String,
|
|
105
|
+
payload: ByteArray,
|
|
106
|
+
packetId: Int? = null,
|
|
107
|
+
qos: Int = 0,
|
|
108
|
+
retain: Boolean = false,
|
|
109
|
+
properties: Map<Int, Any>? = null
|
|
110
|
+
): ByteArray {
|
|
111
|
+
var msgType = MQTTMessageType.PUBLISH.toInt()
|
|
112
|
+
if (qos > 0) msgType = msgType or (qos shl 1)
|
|
113
|
+
if (retain) msgType = msgType or 0x01
|
|
114
|
+
|
|
115
|
+
val vh = mutableListOf<Byte>()
|
|
116
|
+
vh.addAll(MQTTProtocol.encodeString(topic).toList())
|
|
117
|
+
if (qos > 0 && packetId != null) {
|
|
118
|
+
vh.add((packetId shr 8).toByte())
|
|
119
|
+
vh.add((packetId and 0xFF).toByte())
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
val props = properties ?: emptyMap()
|
|
123
|
+
val propsBytes = MQTT5PropertyEncoder.encodeProperties(props)
|
|
124
|
+
val propsLen = MQTTProtocol.encodeRemainingLength(propsBytes.size)
|
|
125
|
+
vh.addAll(propsLen.toList())
|
|
126
|
+
vh.addAll(propsBytes.toList())
|
|
127
|
+
|
|
128
|
+
val pl = (vh + payload.toList()).toByteArray()
|
|
129
|
+
val remLen = pl.size
|
|
130
|
+
return byteArrayOf(
|
|
131
|
+
msgType.toByte(),
|
|
132
|
+
*MQTTProtocol.encodeRemainingLength(remLen),
|
|
133
|
+
*pl
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fun buildSubscribeV5(
|
|
138
|
+
packetId: Int,
|
|
139
|
+
topic: String,
|
|
140
|
+
qos: Int = 0,
|
|
141
|
+
subscriptionIdentifier: Int? = null,
|
|
142
|
+
properties: Map<Int, Any>? = null
|
|
143
|
+
): ByteArray {
|
|
144
|
+
val vh = mutableListOf<Byte>()
|
|
145
|
+
vh.add((packetId shr 8).toByte())
|
|
146
|
+
vh.add((packetId and 0xFF).toByte())
|
|
147
|
+
|
|
148
|
+
val props = mutableMapOf<Int, Any>()
|
|
149
|
+
subscriptionIdentifier?.let { props[MQTT5PropertyType.SUBSCRIPTION_IDENTIFIER.toInt()] = it }
|
|
150
|
+
properties?.let { props.putAll(it) }
|
|
151
|
+
val propsBytes = MQTT5PropertyEncoder.encodeProperties(props)
|
|
152
|
+
val propsLen = MQTTProtocol.encodeRemainingLength(propsBytes.size)
|
|
153
|
+
vh.addAll(propsLen.toList())
|
|
154
|
+
vh.addAll(propsBytes.toList())
|
|
155
|
+
|
|
156
|
+
val pl = MQTTProtocol.encodeString(topic) + byteArrayOf((qos and 0x03).toByte())
|
|
157
|
+
val rem = vh.size + pl.size
|
|
158
|
+
return byteArrayOf(
|
|
159
|
+
(MQTTMessageType.SUBSCRIBE.toInt() or 0x02).toByte(),
|
|
160
|
+
*MQTTProtocol.encodeRemainingLength(rem),
|
|
161
|
+
*vh.toByteArray(),
|
|
162
|
+
*pl
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fun buildSubackV5(
|
|
167
|
+
packetId: Int,
|
|
168
|
+
reasonCodes: List<Int>,
|
|
169
|
+
properties: Map<Int, Any>? = null
|
|
170
|
+
): ByteArray {
|
|
171
|
+
val vh = mutableListOf<Byte>()
|
|
172
|
+
vh.add((packetId shr 8).toByte())
|
|
173
|
+
vh.add((packetId and 0xFF).toByte())
|
|
174
|
+
|
|
175
|
+
val props = properties ?: emptyMap()
|
|
176
|
+
val propsBytes = MQTT5PropertyEncoder.encodeProperties(props)
|
|
177
|
+
val propsLen = MQTTProtocol.encodeRemainingLength(propsBytes.size)
|
|
178
|
+
vh.addAll(propsLen.toList())
|
|
179
|
+
vh.addAll(propsBytes.toList())
|
|
180
|
+
|
|
181
|
+
val pl = reasonCodes.map { it.toByte() }.toByteArray()
|
|
182
|
+
val rem = vh.size + pl.size
|
|
183
|
+
return byteArrayOf(
|
|
184
|
+
MQTTMessageType.SUBACK,
|
|
185
|
+
*MQTTProtocol.encodeRemainingLength(rem),
|
|
186
|
+
*vh.toByteArray(),
|
|
187
|
+
*pl
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
fun parseSubackV5(data: ByteArray, offset: Int = 0): Triple<Int, List<Int>, Map<Int, Any>> {
|
|
192
|
+
if (offset + 2 > data.size) throw IllegalArgumentException("Insufficient data for SUBACK packet ID")
|
|
193
|
+
val pid = ((data[offset].toInt() and 0xFF) shl 8) or (data[offset + 1].toInt() and 0xFF)
|
|
194
|
+
var pos = offset + 2
|
|
195
|
+
|
|
196
|
+
val (propLen, propLenBytes) = MQTTProtocol.decodeRemainingLength(data, pos)
|
|
197
|
+
pos += propLenBytes
|
|
198
|
+
val (props, _) = MQTT5PropertyEncoder.decodeProperties(data.copyOfRange(pos, pos + propLen), 0)
|
|
199
|
+
pos += propLen
|
|
200
|
+
|
|
201
|
+
val reasonCodes = mutableListOf<Int>()
|
|
202
|
+
while (pos < data.size) {
|
|
203
|
+
reasonCodes.add(data[pos].toInt() and 0xFF)
|
|
204
|
+
pos++
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return Triple(pid, reasonCodes, props)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
fun buildUnsubscribeV5(
|
|
211
|
+
packetId: Int,
|
|
212
|
+
topics: List<String>,
|
|
213
|
+
properties: Map<Int, Any>? = null
|
|
214
|
+
): ByteArray {
|
|
215
|
+
val vh = mutableListOf<Byte>()
|
|
216
|
+
vh.add((packetId shr 8).toByte())
|
|
217
|
+
vh.add((packetId and 0xFF).toByte())
|
|
218
|
+
|
|
219
|
+
val props = properties ?: emptyMap()
|
|
220
|
+
val propsBytes = MQTT5PropertyEncoder.encodeProperties(props)
|
|
221
|
+
val propsLen = MQTTProtocol.encodeRemainingLength(propsBytes.size)
|
|
222
|
+
vh.addAll(propsLen.toList())
|
|
223
|
+
vh.addAll(propsBytes.toList())
|
|
224
|
+
|
|
225
|
+
val plList = topics.flatMap { MQTTProtocol.encodeString(it).toList() }
|
|
226
|
+
val pl = ByteArray(plList.size) { plList[it] }
|
|
227
|
+
val rem = vh.size + pl.size
|
|
228
|
+
return byteArrayOf(
|
|
229
|
+
(MQTTMessageType.UNSUBSCRIBE.toInt() or 0x02).toByte(),
|
|
230
|
+
*MQTTProtocol.encodeRemainingLength(rem),
|
|
231
|
+
*vh.toByteArray(),
|
|
232
|
+
*pl
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
fun buildUnsubackV5(
|
|
237
|
+
packetId: Int,
|
|
238
|
+
reasonCodes: List<Int>? = null,
|
|
239
|
+
properties: Map<Int, Any>? = null
|
|
240
|
+
): ByteArray {
|
|
241
|
+
val vh = mutableListOf<Byte>()
|
|
242
|
+
vh.add((packetId shr 8).toByte())
|
|
243
|
+
vh.add((packetId and 0xFF).toByte())
|
|
244
|
+
|
|
245
|
+
val props = properties ?: emptyMap()
|
|
246
|
+
val propsBytes = MQTT5PropertyEncoder.encodeProperties(props)
|
|
247
|
+
val propsLen = MQTTProtocol.encodeRemainingLength(propsBytes.size)
|
|
248
|
+
vh.addAll(propsLen.toList())
|
|
249
|
+
vh.addAll(propsBytes.toList())
|
|
250
|
+
|
|
251
|
+
val pl = reasonCodes?.map { it.toByte() }?.toByteArray() ?: ByteArray(0)
|
|
252
|
+
val rem = vh.size + pl.size
|
|
253
|
+
return byteArrayOf(
|
|
254
|
+
MQTTMessageType.UNSUBACK,
|
|
255
|
+
*MQTTProtocol.encodeRemainingLength(rem),
|
|
256
|
+
*vh.toByteArray(),
|
|
257
|
+
*pl
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
fun buildDisconnectV5(
|
|
262
|
+
reasonCode: Int = MQTT5ReasonCode.NORMAL_DISCONNECTION_DISC,
|
|
263
|
+
properties: Map<Int, Any>? = null
|
|
264
|
+
): ByteArray {
|
|
265
|
+
val vh = mutableListOf<Byte>()
|
|
266
|
+
vh.add(reasonCode.toByte())
|
|
267
|
+
|
|
268
|
+
val props = properties ?: emptyMap()
|
|
269
|
+
val propsBytes = MQTT5PropertyEncoder.encodeProperties(props)
|
|
270
|
+
val propsLen = MQTTProtocol.encodeRemainingLength(propsBytes.size)
|
|
271
|
+
vh.addAll(propsLen.toList())
|
|
272
|
+
vh.addAll(propsBytes.toList())
|
|
273
|
+
|
|
274
|
+
val remLen = vh.size
|
|
275
|
+
val fixed = mutableListOf<Byte>()
|
|
276
|
+
fixed.add(MQTTMessageType.DISCONNECT)
|
|
277
|
+
fixed.addAll(MQTTProtocol.encodeRemainingLength(remLen).toList())
|
|
278
|
+
|
|
279
|
+
return (fixed + vh).toByteArray()
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
package ai.annadata.mqttquic.mqtt
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MQTT 5.0 Reason Codes. Matches MQTTD mqttd/reason_codes.py.
|
|
5
|
+
*/
|
|
6
|
+
object MQTT5ReasonCode {
|
|
7
|
+
// Success
|
|
8
|
+
const val SUCCESS: Int = 0x00
|
|
9
|
+
const val NORMAL_DISCONNECTION: Int = 0x00
|
|
10
|
+
const val DISCONNECT_WITH_WILL_MESSAGE: Int = 0x04
|
|
11
|
+
|
|
12
|
+
// CONNACK Reason Codes
|
|
13
|
+
const val UNSPECIFIED_ERROR: Int = 0x80
|
|
14
|
+
const val MALFORMED_PACKET: Int = 0x81
|
|
15
|
+
const val PROTOCOL_ERROR: Int = 0x82
|
|
16
|
+
const val IMPLEMENTATION_SPECIFIC_ERROR: Int = 0x83
|
|
17
|
+
const val UNSUPPORTED_PROTOCOL_VERSION: Int = 0x84
|
|
18
|
+
const val CLIENT_IDENTIFIER_NOT_VALID: Int = 0x85
|
|
19
|
+
const val BAD_USER_NAME_OR_PASSWORD: Int = 0x86
|
|
20
|
+
const val NOT_AUTHORIZED: Int = 0x87
|
|
21
|
+
const val SERVER_UNAVAILABLE: Int = 0x88
|
|
22
|
+
const val SERVER_BUSY: Int = 0x89
|
|
23
|
+
const val BANNED: Int = 0x8A
|
|
24
|
+
const val BAD_AUTHENTICATION_METHOD: Int = 0x8C
|
|
25
|
+
const val TOPIC_NAME_INVALID: Int = 0x90
|
|
26
|
+
const val PACKET_TOO_LARGE: Int = 0x95
|
|
27
|
+
const val QUOTA_EXCEEDED: Int = 0x97
|
|
28
|
+
const val PAYLOAD_FORMAT_INVALID: Int = 0x99
|
|
29
|
+
const val RETAIN_NOT_SUPPORTED: Int = 0x9A
|
|
30
|
+
const val QOS_NOT_SUPPORTED: Int = 0x9B
|
|
31
|
+
const val USE_ANOTHER_SERVER: Int = 0x9C
|
|
32
|
+
const val SERVER_MOVED: Int = 0x9D
|
|
33
|
+
const val CONNECTION_RATE_EXCEEDED: Int = 0x9F
|
|
34
|
+
|
|
35
|
+
// PUBACK, PUBREC, PUBREL, PUBCOMP Reason Codes
|
|
36
|
+
const val NO_MATCHING_SUBSCRIBERS: Int = 0x10
|
|
37
|
+
const val UNSPECIFIED_ERROR_PUB: Int = 0x80
|
|
38
|
+
const val IMPLEMENTATION_SPECIFIC_ERROR_PUB: Int = 0x83
|
|
39
|
+
const val NOT_AUTHORIZED_PUB: Int = 0x87
|
|
40
|
+
const val TOPIC_NAME_INVALID_PUB: Int = 0x90
|
|
41
|
+
const val PACKET_IDENTIFIER_IN_USE: Int = 0x91
|
|
42
|
+
const val QUOTA_EXCEEDED_PUB: Int = 0x97
|
|
43
|
+
const val PAYLOAD_FORMAT_INVALID_PUB: Int = 0x99
|
|
44
|
+
|
|
45
|
+
// SUBACK Reason Codes
|
|
46
|
+
const val GRANTED_QOS_0: Int = 0x00
|
|
47
|
+
const val GRANTED_QOS_1: Int = 0x01
|
|
48
|
+
const val GRANTED_QOS_2: Int = 0x02
|
|
49
|
+
const val UNSPECIFIED_ERROR_SUB: Int = 0x80
|
|
50
|
+
const val IMPLEMENTATION_SPECIFIC_ERROR_SUB: Int = 0x83
|
|
51
|
+
const val NOT_AUTHORIZED_SUB: Int = 0x87
|
|
52
|
+
const val TOPIC_FILTER_INVALID: Int = 0x8F
|
|
53
|
+
const val PACKET_IDENTIFIER_IN_USE_SUB: Int = 0x91
|
|
54
|
+
const val QUOTA_EXCEEDED_SUB: Int = 0x97
|
|
55
|
+
const val SHARED_SUBSCRIPTIONS_NOT_SUPPORTED: Int = 0x9E
|
|
56
|
+
const val SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED: Int = 0xA1
|
|
57
|
+
const val WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED: Int = 0xA2
|
|
58
|
+
|
|
59
|
+
// UNSUBACK Reason Codes
|
|
60
|
+
const val SUCCESS_UNSUB: Int = 0x00
|
|
61
|
+
const val NO_SUBSCRIPTION_EXISTED: Int = 0x11
|
|
62
|
+
const val UNSPECIFIED_ERROR_UNSUB: Int = 0x80
|
|
63
|
+
const val IMPLEMENTATION_SPECIFIC_ERROR_UNSUB: Int = 0x83
|
|
64
|
+
const val NOT_AUTHORIZED_UNSUB: Int = 0x87
|
|
65
|
+
const val TOPIC_FILTER_INVALID_UNSUB: Int = 0x8F
|
|
66
|
+
const val PACKET_IDENTIFIER_IN_USE_UNSUB: Int = 0x91
|
|
67
|
+
|
|
68
|
+
// DISCONNECT Reason Codes
|
|
69
|
+
const val NORMAL_DISCONNECTION_DISC: Int = 0x00
|
|
70
|
+
const val DISCONNECT_WITH_WILL_MESSAGE_DISC: Int = 0x04
|
|
71
|
+
const val UNSPECIFIED_ERROR_DISC: Int = 0x80
|
|
72
|
+
const val MALFORMED_PACKET_DISC: Int = 0x81
|
|
73
|
+
const val PROTOCOL_ERROR_DISC: Int = 0x82
|
|
74
|
+
const val IMPLEMENTATION_SPECIFIC_ERROR_DISC: Int = 0x83
|
|
75
|
+
const val NOT_AUTHORIZED_DISC: Int = 0x87
|
|
76
|
+
const val SERVER_BUSY_DISC: Int = 0x89
|
|
77
|
+
const val SERVER_SHUTTING_DOWN: Int = 0x8B
|
|
78
|
+
const val BAD_AUTHENTICATION_METHOD_DISC: Int = 0x8C
|
|
79
|
+
const val KEEP_ALIVE_TIMEOUT: Int = 0x8D
|
|
80
|
+
const val SESSION_TAKEN_OVER: Int = 0x8E
|
|
81
|
+
const val TOPIC_FILTER_INVALID_DISC: Int = 0x8F
|
|
82
|
+
const val TOPIC_NAME_INVALID_DISC: Int = 0x90
|
|
83
|
+
const val RECEIVE_MAXIMUM_EXCEEDED: Int = 0x93
|
|
84
|
+
const val TOPIC_ALIAS_INVALID: Int = 0x94
|
|
85
|
+
const val PACKET_TOO_LARGE_DISC: Int = 0x95
|
|
86
|
+
const val MESSAGE_RATE_TOO_HIGH: Int = 0x96
|
|
87
|
+
const val QUOTA_EXCEEDED_DISC: Int = 0x97
|
|
88
|
+
const val ADMINISTRATIVE_ACTION: Int = 0x98
|
|
89
|
+
const val PAYLOAD_FORMAT_INVALID_DISC: Int = 0x99
|
|
90
|
+
const val RETAIN_NOT_SUPPORTED_DISC: Int = 0x9A
|
|
91
|
+
const val QOS_NOT_SUPPORTED_DISC: Int = 0x9B
|
|
92
|
+
const val USE_ANOTHER_SERVER_DISC: Int = 0x9C
|
|
93
|
+
const val SERVER_MOVED_DISC: Int = 0x9D
|
|
94
|
+
const val SHARED_SUBSCRIPTIONS_NOT_SUPPORTED_DISC: Int = 0x9E
|
|
95
|
+
const val CONNECTION_RATE_EXCEEDED_DISC: Int = 0x9F
|
|
96
|
+
const val MAXIMUM_CONNECT_TIME: Int = 0xA0
|
|
97
|
+
const val SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED_DISC: Int = 0xA1
|
|
98
|
+
const val WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED_DISC: Int = 0xA2
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Compatibility mapping for MQTT 3.1.1 return codes
|
|
102
|
+
val MQTT3_TO_MQTT5_REASON_CODE = mapOf(
|
|
103
|
+
0x00 to MQTT5ReasonCode.SUCCESS,
|
|
104
|
+
0x01 to MQTT5ReasonCode.UNSUPPORTED_PROTOCOL_VERSION,
|
|
105
|
+
0x02 to MQTT5ReasonCode.CLIENT_IDENTIFIER_NOT_VALID,
|
|
106
|
+
0x03 to MQTT5ReasonCode.SERVER_UNAVAILABLE,
|
|
107
|
+
0x04 to MQTT5ReasonCode.BAD_USER_NAME_OR_PASSWORD,
|
|
108
|
+
0x05 to MQTT5ReasonCode.NOT_AUTHORIZED,
|
|
109
|
+
)
|