@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,280 @@
|
|
|
1
|
+
//
|
|
2
|
+
// MQTT5Properties.swift
|
|
3
|
+
// MqttQuicPlugin
|
|
4
|
+
//
|
|
5
|
+
// MQTT 5.0 Properties encoder/decoder. Matches MQTTD mqttd/properties.py.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
|
|
10
|
+
public enum MQTT5PropertyType: UInt8 {
|
|
11
|
+
case payloadFormatIndicator = 0x01
|
|
12
|
+
case messageExpiryInterval = 0x02
|
|
13
|
+
case contentType = 0x03
|
|
14
|
+
case responseTopic = 0x08
|
|
15
|
+
case correlationData = 0x09
|
|
16
|
+
case subscriptionIdentifier = 0x0B
|
|
17
|
+
case sessionExpiryInterval = 0x11
|
|
18
|
+
case assignedClientIdentifier = 0x12
|
|
19
|
+
case serverKeepAlive = 0x13
|
|
20
|
+
case authenticationMethod = 0x15
|
|
21
|
+
case authenticationData = 0x16
|
|
22
|
+
case requestProblemInformation = 0x17
|
|
23
|
+
case willDelayInterval = 0x18
|
|
24
|
+
case requestResponseInformation = 0x19
|
|
25
|
+
case responseInformation = 0x1A
|
|
26
|
+
case serverReference = 0x1C
|
|
27
|
+
case reasonString = 0x1F
|
|
28
|
+
case receiveMaximum = 0x21
|
|
29
|
+
case topicAliasMaximum = 0x22
|
|
30
|
+
case topicAlias = 0x23
|
|
31
|
+
case maximumQoS = 0x24
|
|
32
|
+
case retainAvailable = 0x25
|
|
33
|
+
case userProperty = 0x26
|
|
34
|
+
case maximumPacketSize = 0x27
|
|
35
|
+
case wildcardSubscriptionAvailable = 0x28
|
|
36
|
+
case subscriptionIdentifierAvailable = 0x29
|
|
37
|
+
case sharedSubscriptionAvailable = 0x2A
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public struct MQTT5Properties {
|
|
41
|
+
public var properties: [UInt8: Any] = [:]
|
|
42
|
+
|
|
43
|
+
public init() {}
|
|
44
|
+
|
|
45
|
+
public mutating func set(_ type: MQTT5PropertyType, _ value: Any) {
|
|
46
|
+
properties[type.rawValue] = value
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public func get(_ type: MQTT5PropertyType) -> Any? {
|
|
50
|
+
return properties[type.rawValue]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public final class MQTT5PropertyEncoder {
|
|
55
|
+
|
|
56
|
+
public static func encodeProperties(_ props: [UInt8: Any]) throws -> Data {
|
|
57
|
+
var result = Data()
|
|
58
|
+
let sorted = props.sorted { $0.key < $1.key }
|
|
59
|
+
|
|
60
|
+
for (propId, value) in sorted {
|
|
61
|
+
// Handle subscription identifier list specially
|
|
62
|
+
if propId == MQTT5PropertyType.subscriptionIdentifier.rawValue, let list = value as? [Int] {
|
|
63
|
+
for subId in list {
|
|
64
|
+
result.append(propId)
|
|
65
|
+
result.append(contentsOf: try encodeVariableByteInteger(subId))
|
|
66
|
+
}
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
result.append(propId)
|
|
71
|
+
|
|
72
|
+
switch propId {
|
|
73
|
+
case MQTT5PropertyType.payloadFormatIndicator.rawValue:
|
|
74
|
+
result.append((value as? Int ?? 0) & 0xFF)
|
|
75
|
+
|
|
76
|
+
case MQTT5PropertyType.messageExpiryInterval.rawValue,
|
|
77
|
+
MQTT5PropertyType.sessionExpiryInterval.rawValue,
|
|
78
|
+
MQTT5PropertyType.willDelayInterval.rawValue,
|
|
79
|
+
MQTT5PropertyType.maximumPacketSize.rawValue:
|
|
80
|
+
let v = (value as? UInt32 ?? 0)
|
|
81
|
+
result.append(UInt8((v >> 24) & 0xFF))
|
|
82
|
+
result.append(UInt8((v >> 16) & 0xFF))
|
|
83
|
+
result.append(UInt8((v >> 8) & 0xFF))
|
|
84
|
+
result.append(UInt8(v & 0xFF))
|
|
85
|
+
|
|
86
|
+
case MQTT5PropertyType.contentType.rawValue,
|
|
87
|
+
MQTT5PropertyType.responseTopic.rawValue,
|
|
88
|
+
MQTT5PropertyType.assignedClientIdentifier.rawValue,
|
|
89
|
+
MQTT5PropertyType.authenticationMethod.rawValue,
|
|
90
|
+
MQTT5PropertyType.responseInformation.rawValue,
|
|
91
|
+
MQTT5PropertyType.serverReference.rawValue,
|
|
92
|
+
MQTT5PropertyType.reasonString.rawValue:
|
|
93
|
+
result.append(try encodeString(value as? String ?? ""))
|
|
94
|
+
|
|
95
|
+
case MQTT5PropertyType.correlationData.rawValue,
|
|
96
|
+
MQTT5PropertyType.authenticationData.rawValue:
|
|
97
|
+
let data = value as? Data ?? Data()
|
|
98
|
+
result.append(UInt8((data.count >> 8) & 0xFF))
|
|
99
|
+
result.append(UInt8(data.count & 0xFF))
|
|
100
|
+
result.append(data)
|
|
101
|
+
|
|
102
|
+
case MQTT5PropertyType.subscriptionIdentifier.rawValue:
|
|
103
|
+
result.append(contentsOf: try encodeVariableByteInteger(value as? Int ?? 0))
|
|
104
|
+
|
|
105
|
+
case MQTT5PropertyType.serverKeepAlive.rawValue,
|
|
106
|
+
MQTT5PropertyType.receiveMaximum.rawValue,
|
|
107
|
+
MQTT5PropertyType.topicAliasMaximum.rawValue,
|
|
108
|
+
MQTT5PropertyType.topicAlias.rawValue:
|
|
109
|
+
let v = (value as? UInt16 ?? 0)
|
|
110
|
+
result.append(UInt8((v >> 8) & 0xFF))
|
|
111
|
+
result.append(UInt8(v & 0xFF))
|
|
112
|
+
|
|
113
|
+
case MQTT5PropertyType.maximumQoS.rawValue,
|
|
114
|
+
MQTT5PropertyType.retainAvailable.rawValue,
|
|
115
|
+
MQTT5PropertyType.requestProblemInformation.rawValue,
|
|
116
|
+
MQTT5PropertyType.requestResponseInformation.rawValue,
|
|
117
|
+
MQTT5PropertyType.wildcardSubscriptionAvailable.rawValue,
|
|
118
|
+
MQTT5PropertyType.subscriptionIdentifierAvailable.rawValue,
|
|
119
|
+
MQTT5PropertyType.sharedSubscriptionAvailable.rawValue:
|
|
120
|
+
result.append((value as? Int ?? 0) & 0xFF)
|
|
121
|
+
|
|
122
|
+
case MQTT5PropertyType.userProperty.rawValue:
|
|
123
|
+
if let pair = value as? (String, String) {
|
|
124
|
+
result.append(try encodeString(pair.0))
|
|
125
|
+
result.append(try encodeString(pair.1))
|
|
126
|
+
} else {
|
|
127
|
+
throw MQTTProtocolError.insufficientData("USER_PROPERTY must be (String, String)")
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
default:
|
|
131
|
+
throw MQTTProtocolError.insufficientData("Unknown property type: \(propId)")
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return result
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public static func decodeProperties(_ data: Data, offset: Int = 0) throws -> ([UInt8: Any], Int) {
|
|
139
|
+
var props: [UInt8: Any] = [:]
|
|
140
|
+
var pos = offset
|
|
141
|
+
|
|
142
|
+
while pos < data.count {
|
|
143
|
+
let propId = data[pos]
|
|
144
|
+
pos += 1
|
|
145
|
+
|
|
146
|
+
switch propId {
|
|
147
|
+
case MQTT5PropertyType.payloadFormatIndicator.rawValue:
|
|
148
|
+
props[propId] = Int(data[pos] & 0xFF)
|
|
149
|
+
pos += 1
|
|
150
|
+
|
|
151
|
+
case MQTT5PropertyType.messageExpiryInterval.rawValue,
|
|
152
|
+
MQTT5PropertyType.sessionExpiryInterval.rawValue,
|
|
153
|
+
MQTT5PropertyType.willDelayInterval.rawValue,
|
|
154
|
+
MQTT5PropertyType.maximumPacketSize.rawValue:
|
|
155
|
+
let v = (UInt32(data[pos] & 0xFF) << 24) |
|
|
156
|
+
(UInt32(data[pos + 1] & 0xFF) << 16) |
|
|
157
|
+
(UInt32(data[pos + 2] & 0xFF) << 8) |
|
|
158
|
+
UInt32(data[pos + 3] & 0xFF)
|
|
159
|
+
props[propId] = v
|
|
160
|
+
pos += 4
|
|
161
|
+
|
|
162
|
+
case MQTT5PropertyType.contentType.rawValue,
|
|
163
|
+
MQTT5PropertyType.responseTopic.rawValue,
|
|
164
|
+
MQTT5PropertyType.assignedClientIdentifier.rawValue,
|
|
165
|
+
MQTT5PropertyType.authenticationMethod.rawValue,
|
|
166
|
+
MQTT5PropertyType.responseInformation.rawValue,
|
|
167
|
+
MQTT5PropertyType.serverReference.rawValue,
|
|
168
|
+
MQTT5PropertyType.reasonString.rawValue:
|
|
169
|
+
let (s, next) = try decodeString(data, offset: pos)
|
|
170
|
+
props[propId] = s
|
|
171
|
+
pos = next
|
|
172
|
+
|
|
173
|
+
case MQTT5PropertyType.correlationData.rawValue,
|
|
174
|
+
MQTT5PropertyType.authenticationData.rawValue:
|
|
175
|
+
let len = (Int(data[pos] & 0xFF) << 8) | Int(data[pos + 1] & 0xFF)
|
|
176
|
+
pos += 2
|
|
177
|
+
props[propId] = data.subdata(in: pos..<(pos + len))
|
|
178
|
+
pos += len
|
|
179
|
+
|
|
180
|
+
case MQTT5PropertyType.subscriptionIdentifier.rawValue:
|
|
181
|
+
let (v, consumed) = try decodeVariableByteInteger(data, offset: pos)
|
|
182
|
+
if props[propId] == nil {
|
|
183
|
+
props[propId] = [v]
|
|
184
|
+
} else if var list = props[propId] as? [Int] {
|
|
185
|
+
list.append(v)
|
|
186
|
+
props[propId] = list
|
|
187
|
+
} else {
|
|
188
|
+
props[propId] = [v]
|
|
189
|
+
}
|
|
190
|
+
pos += consumed
|
|
191
|
+
|
|
192
|
+
case MQTT5PropertyType.serverKeepAlive.rawValue,
|
|
193
|
+
MQTT5PropertyType.receiveMaximum.rawValue,
|
|
194
|
+
MQTT5PropertyType.topicAliasMaximum.rawValue,
|
|
195
|
+
MQTT5PropertyType.topicAlias.rawValue:
|
|
196
|
+
let v = (UInt16(data[pos] & 0xFF) << 8) | UInt16(data[pos + 1] & 0xFF)
|
|
197
|
+
props[propId] = v
|
|
198
|
+
pos += 2
|
|
199
|
+
|
|
200
|
+
case MQTT5PropertyType.maximumQoS.rawValue,
|
|
201
|
+
MQTT5PropertyType.retainAvailable.rawValue,
|
|
202
|
+
MQTT5PropertyType.requestProblemInformation.rawValue,
|
|
203
|
+
MQTT5PropertyType.requestResponseInformation.rawValue,
|
|
204
|
+
MQTT5PropertyType.wildcardSubscriptionAvailable.rawValue,
|
|
205
|
+
MQTT5PropertyType.subscriptionIdentifierAvailable.rawValue,
|
|
206
|
+
MQTT5PropertyType.sharedSubscriptionAvailable.rawValue:
|
|
207
|
+
props[propId] = Int(data[pos] & 0xFF)
|
|
208
|
+
pos += 1
|
|
209
|
+
|
|
210
|
+
case MQTT5PropertyType.userProperty.rawValue:
|
|
211
|
+
let (name, next1) = try decodeString(data, offset: pos)
|
|
212
|
+
pos = next1
|
|
213
|
+
let (value, next2) = try decodeString(data, offset: pos)
|
|
214
|
+
pos = next2
|
|
215
|
+
if props[propId] == nil {
|
|
216
|
+
props[propId] = [(name, value)]
|
|
217
|
+
} else if var list = props[propId] as? [(String, String)] {
|
|
218
|
+
list.append((name, value))
|
|
219
|
+
props[propId] = list
|
|
220
|
+
} else {
|
|
221
|
+
props[propId] = [(name, value)]
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
default:
|
|
225
|
+
// Unknown property - skip (per spec)
|
|
226
|
+
break
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return (props, pos - offset)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private static func encodeString(_ s: String) throws -> Data {
|
|
234
|
+
guard let utf8 = s.data(using: .utf8) else { throw MQTTProtocolError.invalidUTF8 }
|
|
235
|
+
if utf8.count > 0xFFFF { throw MQTTProtocolError.insufficientData("string too long") }
|
|
236
|
+
var out = Data()
|
|
237
|
+
out.append(UInt8((utf8.count >> 8) & 0xFF))
|
|
238
|
+
out.append(UInt8(utf8.count & 0xFF))
|
|
239
|
+
out.append(utf8)
|
|
240
|
+
return out
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private static func decodeString(_ data: Data, offset: Int) throws -> (String, Int) {
|
|
244
|
+
if offset + 2 > data.count { throw MQTTProtocolError.insufficientData("string length") }
|
|
245
|
+
let len = (Int(data[offset] & 0xFF) << 8) | Int(data[offset + 1] & 0xFF)
|
|
246
|
+
let start = offset + 2
|
|
247
|
+
if start + len > data.count { throw MQTTProtocolError.insufficientData("string content") }
|
|
248
|
+
let sub = data.subdata(in: start..<(start + len))
|
|
249
|
+
guard let s = String(data: sub, encoding: .utf8) else { throw MQTTProtocolError.invalidUTF8 }
|
|
250
|
+
return (s, start + len)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private static func encodeVariableByteInteger(_ value: Int) throws -> Data {
|
|
254
|
+
if value < 0 || value > 268_435_455 { throw MQTTProtocolError.invalidRemainingLength(value) }
|
|
255
|
+
var enc: [UInt8] = []
|
|
256
|
+
var n = value
|
|
257
|
+
repeat {
|
|
258
|
+
var b = UInt8(n % 128)
|
|
259
|
+
n /= 128
|
|
260
|
+
if n > 0 { b |= 0x80 }
|
|
261
|
+
enc.append(b)
|
|
262
|
+
} while n > 0
|
|
263
|
+
return Data(enc)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private static func decodeVariableByteInteger(_ data: Data, offset: Int) throws -> (Int, Int) {
|
|
267
|
+
var mul = 1
|
|
268
|
+
var val = 0
|
|
269
|
+
var i = offset
|
|
270
|
+
for _ in 0..<4 {
|
|
271
|
+
if i >= data.count { throw MQTTProtocolError.insufficientData("variable byte integer") }
|
|
272
|
+
let b = data[i]
|
|
273
|
+
val += Int(b & 0x7F) * mul
|
|
274
|
+
i += 1
|
|
275
|
+
if (b & 0x80) == 0 { break }
|
|
276
|
+
mul *= 128
|
|
277
|
+
}
|
|
278
|
+
return (val, i - offset)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
//
|
|
2
|
+
// MQTT5Protocol.swift
|
|
3
|
+
// MqttQuicPlugin
|
|
4
|
+
//
|
|
5
|
+
// MQTT 5.0 Protocol implementation. Matches MQTTD mqttd/protocol_v5.py.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
|
|
10
|
+
public final class MQTT5Protocol {
|
|
11
|
+
|
|
12
|
+
public static func buildConnectV5(
|
|
13
|
+
clientId: String,
|
|
14
|
+
username: String? = nil,
|
|
15
|
+
password: String? = nil,
|
|
16
|
+
keepalive: UInt16 = 60,
|
|
17
|
+
cleanStart: Bool = true,
|
|
18
|
+
sessionExpiryInterval: UInt32? = nil,
|
|
19
|
+
receiveMaximum: UInt16? = nil,
|
|
20
|
+
maximumPacketSize: UInt32? = nil,
|
|
21
|
+
topicAliasMaximum: UInt16? = nil,
|
|
22
|
+
requestResponseInformation: UInt8? = nil,
|
|
23
|
+
requestProblemInformation: UInt8? = nil,
|
|
24
|
+
authenticationMethod: String? = nil,
|
|
25
|
+
authenticationData: Data? = nil,
|
|
26
|
+
properties: [UInt8: Any]? = nil
|
|
27
|
+
) throws -> Data {
|
|
28
|
+
var variableHeader = Data()
|
|
29
|
+
variableHeader.append(try MQTTProtocol.encodeString("MQTT"))
|
|
30
|
+
variableHeader.append(MQTTProtocolLevel.v5)
|
|
31
|
+
|
|
32
|
+
var flags: UInt8 = 0
|
|
33
|
+
if cleanStart { flags |= 0x02 }
|
|
34
|
+
if username != nil { flags |= MQTTConnectFlags.username }
|
|
35
|
+
if password != nil { flags |= MQTTConnectFlags.password }
|
|
36
|
+
variableHeader.append(flags)
|
|
37
|
+
variableHeader.append(UInt8((keepalive >> 8) & 0xFF))
|
|
38
|
+
variableHeader.append(UInt8(keepalive & 0xFF))
|
|
39
|
+
|
|
40
|
+
var connectProps: [UInt8: Any] = [:]
|
|
41
|
+
if let sei = sessionExpiryInterval {
|
|
42
|
+
connectProps[MQTT5PropertyType.sessionExpiryInterval.rawValue] = sei
|
|
43
|
+
}
|
|
44
|
+
if let rm = receiveMaximum {
|
|
45
|
+
connectProps[MQTT5PropertyType.receiveMaximum.rawValue] = rm
|
|
46
|
+
}
|
|
47
|
+
if let mps = maximumPacketSize {
|
|
48
|
+
connectProps[MQTT5PropertyType.maximumPacketSize.rawValue] = mps
|
|
49
|
+
}
|
|
50
|
+
if let tam = topicAliasMaximum {
|
|
51
|
+
connectProps[MQTT5PropertyType.topicAliasMaximum.rawValue] = tam
|
|
52
|
+
}
|
|
53
|
+
if let rri = requestResponseInformation {
|
|
54
|
+
connectProps[MQTT5PropertyType.requestResponseInformation.rawValue] = rri
|
|
55
|
+
}
|
|
56
|
+
if let rpi = requestProblemInformation {
|
|
57
|
+
connectProps[MQTT5PropertyType.requestProblemInformation.rawValue] = rpi
|
|
58
|
+
}
|
|
59
|
+
if let am = authenticationMethod {
|
|
60
|
+
connectProps[MQTT5PropertyType.authenticationMethod.rawValue] = am
|
|
61
|
+
}
|
|
62
|
+
if let ad = authenticationData {
|
|
63
|
+
connectProps[MQTT5PropertyType.authenticationData.rawValue] = ad
|
|
64
|
+
}
|
|
65
|
+
if let props = properties {
|
|
66
|
+
connectProps.merge(props) { (_, new) in new }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let propsBytes = try MQTT5PropertyEncoder.encodeProperties(connectProps)
|
|
70
|
+
let propsLen = try MQTTProtocol.encodeRemainingLength(propsBytes.count)
|
|
71
|
+
variableHeader.append(contentsOf: propsLen)
|
|
72
|
+
variableHeader.append(propsBytes)
|
|
73
|
+
|
|
74
|
+
var payload = Data()
|
|
75
|
+
payload.append(try MQTTProtocol.encodeString(clientId))
|
|
76
|
+
payload.append(0x00) // Will Properties length = 0 (no will for now)
|
|
77
|
+
if let u = username { payload.append(try MQTTProtocol.encodeString(u)) }
|
|
78
|
+
if let p = password { payload.append(try MQTTProtocol.encodeString(p)) }
|
|
79
|
+
|
|
80
|
+
let remLen = variableHeader.count + payload.count
|
|
81
|
+
var fixed = Data()
|
|
82
|
+
fixed.append(MQTTMessageType.CONNECT.rawValue)
|
|
83
|
+
fixed.append(contentsOf: try MQTTProtocol.encodeRemainingLength(remLen))
|
|
84
|
+
|
|
85
|
+
var out = Data()
|
|
86
|
+
out.append(fixed)
|
|
87
|
+
out.append(variableHeader)
|
|
88
|
+
out.append(payload)
|
|
89
|
+
return out
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public static func buildConnackV5(
|
|
93
|
+
reasonCode: MQTT5ReasonCode = .success,
|
|
94
|
+
sessionPresent: Bool = false,
|
|
95
|
+
properties: [UInt8: Any]? = nil
|
|
96
|
+
) throws -> Data {
|
|
97
|
+
var variableHeader = Data()
|
|
98
|
+
variableHeader.append(sessionPresent ? 0x01 : 0x00)
|
|
99
|
+
variableHeader.append(reasonCode.rawValue)
|
|
100
|
+
|
|
101
|
+
var props: [UInt8: Any] = [:]
|
|
102
|
+
if let p = properties { props = p }
|
|
103
|
+
let propsBytes = try MQTT5PropertyEncoder.encodeProperties(props)
|
|
104
|
+
let propsLen = try MQTTProtocol.encodeRemainingLength(propsBytes.count)
|
|
105
|
+
variableHeader.append(contentsOf: propsLen)
|
|
106
|
+
variableHeader.append(propsBytes)
|
|
107
|
+
|
|
108
|
+
let remLen = variableHeader.count
|
|
109
|
+
var out = Data()
|
|
110
|
+
out.append(MQTTMessageType.CONNACK.rawValue)
|
|
111
|
+
out.append(contentsOf: try MQTTProtocol.encodeRemainingLength(remLen))
|
|
112
|
+
out.append(variableHeader)
|
|
113
|
+
return out
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public static func parseConnackV5(_ data: Data, offset: Int = 0) throws -> (Bool, MQTT5ReasonCode, [UInt8: Any], Int) {
|
|
117
|
+
if offset + 2 > data.count { throw MQTTProtocolError.insufficientData("CONNACK") }
|
|
118
|
+
let sessionPresent = (data[offset] & 0x01) != 0
|
|
119
|
+
let reasonCode = MQTT5ReasonCode(rawValue: data[offset + 1]) ?? .unspecifiedError
|
|
120
|
+
var pos = offset + 2
|
|
121
|
+
|
|
122
|
+
let (propLen, propLenBytes) = try MQTTProtocol.decodeRemainingLength(data, offset: pos)
|
|
123
|
+
pos += propLenBytes
|
|
124
|
+
let (props, _) = try MQTT5PropertyEncoder.decodeProperties(data.subdata(in: pos..<(pos + propLen)), offset: 0)
|
|
125
|
+
pos += propLen
|
|
126
|
+
|
|
127
|
+
return (sessionPresent, reasonCode, props, pos)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public static func buildPublishV5(
|
|
131
|
+
topic: String,
|
|
132
|
+
payload: Data,
|
|
133
|
+
packetId: UInt16? = nil,
|
|
134
|
+
qos: UInt8 = 0,
|
|
135
|
+
retain: Bool = false,
|
|
136
|
+
properties: [UInt8: Any]? = nil
|
|
137
|
+
) throws -> Data {
|
|
138
|
+
var msgType = MQTTMessageType.PUBLISH.rawValue
|
|
139
|
+
if qos > 0 { msgType |= (qos << 1) }
|
|
140
|
+
if retain { msgType |= 0x01 }
|
|
141
|
+
|
|
142
|
+
var vh = Data()
|
|
143
|
+
vh.append(try MQTTProtocol.encodeString(topic))
|
|
144
|
+
if qos > 0, let pid = packetId {
|
|
145
|
+
vh.append(UInt8((pid >> 8) & 0xFF))
|
|
146
|
+
vh.append(UInt8(pid & 0xFF))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
var props: [UInt8: Any] = [:]
|
|
150
|
+
if let p = properties { props = p }
|
|
151
|
+
let propsBytes = try MQTT5PropertyEncoder.encodeProperties(props)
|
|
152
|
+
let propsLen = try MQTTProtocol.encodeRemainingLength(propsBytes.count)
|
|
153
|
+
vh.append(contentsOf: propsLen)
|
|
154
|
+
vh.append(propsBytes)
|
|
155
|
+
|
|
156
|
+
let pl = vh + payload
|
|
157
|
+
let remLen = pl.count
|
|
158
|
+
var out = Data()
|
|
159
|
+
out.append(msgType)
|
|
160
|
+
out.append(contentsOf: try MQTTProtocol.encodeRemainingLength(remLen))
|
|
161
|
+
out.append(pl)
|
|
162
|
+
return out
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public static func buildSubscribeV5(
|
|
166
|
+
packetId: UInt16,
|
|
167
|
+
topic: String,
|
|
168
|
+
qos: UInt8 = 0,
|
|
169
|
+
subscriptionIdentifier: Int? = nil,
|
|
170
|
+
properties: [UInt8: Any]? = nil
|
|
171
|
+
) throws -> Data {
|
|
172
|
+
var vh = Data()
|
|
173
|
+
vh.append(UInt8((packetId >> 8) & 0xFF))
|
|
174
|
+
vh.append(UInt8(packetId & 0xFF))
|
|
175
|
+
|
|
176
|
+
var props: [UInt8: Any] = [:]
|
|
177
|
+
if let si = subscriptionIdentifier {
|
|
178
|
+
props[MQTT5PropertyType.subscriptionIdentifier.rawValue] = si
|
|
179
|
+
}
|
|
180
|
+
if let p = properties { props.merge(p) { (_, new) in new } }
|
|
181
|
+
let propsBytes = try MQTT5PropertyEncoder.encodeProperties(props)
|
|
182
|
+
let propsLen = try MQTTProtocol.encodeRemainingLength(propsBytes.count)
|
|
183
|
+
vh.append(contentsOf: propsLen)
|
|
184
|
+
vh.append(propsBytes)
|
|
185
|
+
|
|
186
|
+
var pl = Data()
|
|
187
|
+
pl.append(try MQTTProtocol.encodeString(topic))
|
|
188
|
+
pl.append(qos & 0x03)
|
|
189
|
+
|
|
190
|
+
let rem = vh.count + pl.count
|
|
191
|
+
var out = Data()
|
|
192
|
+
out.append(MQTTMessageType.SUBSCRIBE.rawValue | 0x02)
|
|
193
|
+
out.append(contentsOf: try MQTTProtocol.encodeRemainingLength(rem))
|
|
194
|
+
out.append(vh)
|
|
195
|
+
out.append(pl)
|
|
196
|
+
return out
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public static func buildSubackV5(
|
|
200
|
+
packetId: UInt16,
|
|
201
|
+
reasonCodes: [MQTT5ReasonCode],
|
|
202
|
+
properties: [UInt8: Any]? = nil
|
|
203
|
+
) throws -> Data {
|
|
204
|
+
var vh = Data()
|
|
205
|
+
vh.append(UInt8((packetId >> 8) & 0xFF))
|
|
206
|
+
vh.append(UInt8(packetId & 0xFF))
|
|
207
|
+
|
|
208
|
+
var props: [UInt8: Any] = [:]
|
|
209
|
+
if let p = properties { props = p }
|
|
210
|
+
let propsBytes = try MQTT5PropertyEncoder.encodeProperties(props)
|
|
211
|
+
let propsLen = try MQTTProtocol.encodeRemainingLength(propsBytes.count)
|
|
212
|
+
vh.append(contentsOf: propsLen)
|
|
213
|
+
vh.append(propsBytes)
|
|
214
|
+
|
|
215
|
+
var pl = Data()
|
|
216
|
+
for rc in reasonCodes {
|
|
217
|
+
pl.append(rc.rawValue)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let rem = vh.count + pl.count
|
|
221
|
+
var out = Data()
|
|
222
|
+
out.append(MQTTMessageType.SUBACK.rawValue)
|
|
223
|
+
out.append(contentsOf: try MQTTProtocol.encodeRemainingLength(rem))
|
|
224
|
+
out.append(vh)
|
|
225
|
+
out.append(pl)
|
|
226
|
+
return out
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
public static func parseSubackV5(_ data: Data, offset: Int = 0) throws -> (UInt16, [MQTT5ReasonCode], [UInt8: Any], Int) {
|
|
230
|
+
if offset + 2 > data.count { throw MQTTProtocolError.insufficientData("SUBACK packet ID") }
|
|
231
|
+
let pid = (UInt16(data[offset] & 0xFF) << 8) | UInt16(data[offset + 1] & 0xFF)
|
|
232
|
+
var pos = offset + 2
|
|
233
|
+
|
|
234
|
+
let (propLen, propLenBytes) = try MQTTProtocol.decodeRemainingLength(data, offset: pos)
|
|
235
|
+
pos += propLenBytes
|
|
236
|
+
let (props, _) = try MQTT5PropertyEncoder.decodeProperties(data.subdata(in: pos..<(pos + propLen)), offset: 0)
|
|
237
|
+
pos += propLen
|
|
238
|
+
|
|
239
|
+
var reasonCodes: [MQTT5ReasonCode] = []
|
|
240
|
+
while pos < data.count {
|
|
241
|
+
if let rc = MQTT5ReasonCode(rawValue: data[pos]) {
|
|
242
|
+
reasonCodes.append(rc)
|
|
243
|
+
}
|
|
244
|
+
pos += 1
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return (pid, reasonCodes, props, pos)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
public static func buildUnsubscribeV5(
|
|
251
|
+
packetId: UInt16,
|
|
252
|
+
topics: [String],
|
|
253
|
+
properties: [UInt8: Any]? = nil
|
|
254
|
+
) throws -> Data {
|
|
255
|
+
var vh = Data()
|
|
256
|
+
vh.append(UInt8((packetId >> 8) & 0xFF))
|
|
257
|
+
vh.append(UInt8(packetId & 0xFF))
|
|
258
|
+
|
|
259
|
+
var props: [UInt8: Any] = [:]
|
|
260
|
+
if let p = properties { props = p }
|
|
261
|
+
let propsBytes = try MQTT5PropertyEncoder.encodeProperties(props)
|
|
262
|
+
let propsLen = try MQTTProtocol.encodeRemainingLength(propsBytes.count)
|
|
263
|
+
vh.append(contentsOf: propsLen)
|
|
264
|
+
vh.append(propsBytes)
|
|
265
|
+
|
|
266
|
+
var pl = Data()
|
|
267
|
+
for t in topics {
|
|
268
|
+
pl.append(try MQTTProtocol.encodeString(t))
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let rem = vh.count + pl.count
|
|
272
|
+
var out = Data()
|
|
273
|
+
out.append(MQTTMessageType.UNSUBSCRIBE.rawValue | 0x02)
|
|
274
|
+
out.append(contentsOf: try MQTTProtocol.encodeRemainingLength(rem))
|
|
275
|
+
out.append(vh)
|
|
276
|
+
out.append(pl)
|
|
277
|
+
return out
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
public static func buildUnsubackV5(
|
|
281
|
+
packetId: UInt16,
|
|
282
|
+
reasonCodes: [MQTT5ReasonCode]? = nil,
|
|
283
|
+
properties: [UInt8: Any]? = nil
|
|
284
|
+
) throws -> Data {
|
|
285
|
+
var vh = Data()
|
|
286
|
+
vh.append(UInt8((packetId >> 8) & 0xFF))
|
|
287
|
+
vh.append(UInt8(packetId & 0xFF))
|
|
288
|
+
|
|
289
|
+
var props: [UInt8: Any] = [:]
|
|
290
|
+
if let p = properties { props = p }
|
|
291
|
+
let propsBytes = try MQTT5PropertyEncoder.encodeProperties(props)
|
|
292
|
+
let propsLen = try MQTTProtocol.encodeRemainingLength(propsBytes.count)
|
|
293
|
+
vh.append(contentsOf: propsLen)
|
|
294
|
+
vh.append(propsBytes)
|
|
295
|
+
|
|
296
|
+
var pl = Data()
|
|
297
|
+
if let rcs = reasonCodes {
|
|
298
|
+
for rc in rcs {
|
|
299
|
+
pl.append(rc.rawValue)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let rem = vh.count + pl.count
|
|
304
|
+
var out = Data()
|
|
305
|
+
out.append(MQTTMessageType.UNSUBACK.rawValue)
|
|
306
|
+
out.append(contentsOf: try MQTTProtocol.encodeRemainingLength(rem))
|
|
307
|
+
out.append(vh)
|
|
308
|
+
out.append(pl)
|
|
309
|
+
return out
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
public static func buildDisconnectV5(
|
|
313
|
+
reasonCode: MQTT5ReasonCode = .normalDisconnectionDisc,
|
|
314
|
+
properties: [UInt8: Any]? = nil
|
|
315
|
+
) throws -> Data {
|
|
316
|
+
var vh = Data()
|
|
317
|
+
vh.append(reasonCode.rawValue)
|
|
318
|
+
|
|
319
|
+
var props: [UInt8: Any] = [:]
|
|
320
|
+
if let p = properties { props = p }
|
|
321
|
+
let propsBytes = try MQTT5PropertyEncoder.encodeProperties(props)
|
|
322
|
+
let propsLen = try MQTTProtocol.encodeRemainingLength(propsBytes.count)
|
|
323
|
+
vh.append(contentsOf: propsLen)
|
|
324
|
+
vh.append(propsBytes)
|
|
325
|
+
|
|
326
|
+
let remLen = vh.count
|
|
327
|
+
var out = Data()
|
|
328
|
+
out.append(MQTTMessageType.DISCONNECT.rawValue)
|
|
329
|
+
out.append(contentsOf: try MQTTProtocol.encodeRemainingLength(remLen))
|
|
330
|
+
out.append(vh)
|
|
331
|
+
return out
|
|
332
|
+
}
|
|
333
|
+
}
|