@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.
Files changed (64) hide show
  1. package/README.md +399 -0
  2. package/android/NGTCP2_BUILD_INSTRUCTIONS.md +319 -0
  3. package/android/build-nghttp3.sh +182 -0
  4. package/android/build-ngtcp2.sh +289 -0
  5. package/android/build-openssl.sh +302 -0
  6. package/android/build.gradle +75 -0
  7. package/android/gradle.properties +3 -0
  8. package/android/proguard-rules.pro +2 -0
  9. package/android/settings.gradle +1 -0
  10. package/android/src/main/AndroidManifest.xml +1 -0
  11. package/android/src/main/assets/mqttquic_ca.pem +5 -0
  12. package/android/src/main/cpp/CMakeLists.txt +157 -0
  13. package/android/src/main/cpp/ngtcp2_jni.cpp +928 -0
  14. package/android/src/main/kotlin/ai/annadata/mqttquic/MqttQuicPlugin.kt +232 -0
  15. package/android/src/main/kotlin/ai/annadata/mqttquic/client/MQTTClient.kt +339 -0
  16. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5Properties.kt +250 -0
  17. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5Protocol.kt +281 -0
  18. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5ReasonCodes.kt +109 -0
  19. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocol.kt +249 -0
  20. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTTypes.kt +47 -0
  21. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/NGTCP2Client.kt +184 -0
  22. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicClientStub.kt +54 -0
  23. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicTypes.kt +21 -0
  24. package/android/src/main/kotlin/ai/annadata/mqttquic/transport/QUICStreamAdapter.kt +37 -0
  25. package/android/src/main/kotlin/ai/annadata/mqttquic/transport/StreamTransport.kt +91 -0
  26. package/android/src/test/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocolTest.kt +92 -0
  27. package/dist/esm/definitions.d.ts +66 -0
  28. package/dist/esm/definitions.d.ts.map +1 -0
  29. package/dist/esm/definitions.js +2 -0
  30. package/dist/esm/definitions.js.map +1 -0
  31. package/dist/esm/index.d.ts +5 -0
  32. package/dist/esm/index.d.ts.map +1 -0
  33. package/dist/esm/index.js +7 -0
  34. package/dist/esm/index.js.map +1 -0
  35. package/dist/esm/web.d.ts +28 -0
  36. package/dist/esm/web.d.ts.map +1 -0
  37. package/dist/esm/web.js +183 -0
  38. package/dist/esm/web.js.map +1 -0
  39. package/dist/plugin.cjs.js +217 -0
  40. package/dist/plugin.cjs.js.map +1 -0
  41. package/dist/plugin.js +215 -0
  42. package/dist/plugin.js.map +1 -0
  43. package/ios/MqttQuicPlugin.podspec +34 -0
  44. package/ios/NGTCP2_BUILD_INSTRUCTIONS.md +302 -0
  45. package/ios/Sources/MqttQuicPlugin/Client/MQTTClient.swift +343 -0
  46. package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5Properties.swift +280 -0
  47. package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5Protocol.swift +333 -0
  48. package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5ReasonCodes.swift +113 -0
  49. package/ios/Sources/MqttQuicPlugin/MQTT/MQTTProtocol.swift +322 -0
  50. package/ios/Sources/MqttQuicPlugin/MQTT/MQTTTypes.swift +54 -0
  51. package/ios/Sources/MqttQuicPlugin/MqttQuicPlugin.swift +229 -0
  52. package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Bridge.h +29 -0
  53. package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Bridge.mm +865 -0
  54. package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Client.swift +262 -0
  55. package/ios/Sources/MqttQuicPlugin/QUIC/QuicClientStub.swift +66 -0
  56. package/ios/Sources/MqttQuicPlugin/QUIC/QuicTypes.swift +23 -0
  57. package/ios/Sources/MqttQuicPlugin/Resources/mqttquic_ca.pem +5 -0
  58. package/ios/Sources/MqttQuicPlugin/Transport/QUICStreamAdapter.swift +50 -0
  59. package/ios/Sources/MqttQuicPlugin/Transport/StreamTransport.swift +105 -0
  60. package/ios/Tests/MQTTProtocolTests.swift +82 -0
  61. package/ios/build-nghttp3.sh +173 -0
  62. package/ios/build-ngtcp2.sh +278 -0
  63. package/ios/build-openssl.sh +405 -0
  64. 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
+ }