1 |
|
2 | const protocol = module.exports
|
3 |
|
4 |
|
5 | protocol.types = {
|
6 | 0: 'reserved',
|
7 | 1: 'connect',
|
8 | 2: 'connack',
|
9 | 3: 'publish',
|
10 | 4: 'puback',
|
11 | 5: 'pubrec',
|
12 | 6: 'pubrel',
|
13 | 7: 'pubcomp',
|
14 | 8: 'subscribe',
|
15 | 9: 'suback',
|
16 | 10: 'unsubscribe',
|
17 | 11: 'unsuback',
|
18 | 12: 'pingreq',
|
19 | 13: 'pingresp',
|
20 | 14: 'disconnect',
|
21 | 15: 'auth'
|
22 | }
|
23 |
|
24 | protocol.requiredHeaderFlags = {
|
25 | 1: 0,
|
26 | 2: 0,
|
27 | 4: 0,
|
28 | 5: 0,
|
29 | 6: 2,
|
30 | 7: 0,
|
31 | 8: 2,
|
32 | 9: 0,
|
33 | 10: 2,
|
34 | 11: 0,
|
35 | 12: 0,
|
36 | 13: 0,
|
37 | 14: 0,
|
38 | 15: 0
|
39 | }
|
40 |
|
41 | protocol.requiredHeaderFlagsErrors = {}
|
42 | for (const k in protocol.requiredHeaderFlags) {
|
43 | const v = protocol.requiredHeaderFlags[k]
|
44 | protocol.requiredHeaderFlagsErrors[k] = 'Invalid header flag bits, must be 0x' + v.toString(16) + ' for ' + protocol.types[k] + ' packet'
|
45 | }
|
46 |
|
47 |
|
48 | protocol.codes = {}
|
49 | for (const k in protocol.types) {
|
50 | const v = protocol.types[k]
|
51 | protocol.codes[v] = k
|
52 | }
|
53 |
|
54 |
|
55 | protocol.CMD_SHIFT = 4
|
56 | protocol.CMD_MASK = 0xF0
|
57 | protocol.DUP_MASK = 0x08
|
58 | protocol.QOS_MASK = 0x03
|
59 | protocol.QOS_SHIFT = 1
|
60 | protocol.RETAIN_MASK = 0x01
|
61 |
|
62 |
|
63 | protocol.VARBYTEINT_MASK = 0x7F
|
64 | protocol.VARBYTEINT_FIN_MASK = 0x80
|
65 | protocol.VARBYTEINT_MAX = 268435455
|
66 |
|
67 |
|
68 | protocol.SESSIONPRESENT_MASK = 0x01
|
69 | protocol.SESSIONPRESENT_HEADER = Buffer.from([protocol.SESSIONPRESENT_MASK])
|
70 | protocol.CONNACK_HEADER = Buffer.from([protocol.codes.connack << protocol.CMD_SHIFT])
|
71 |
|
72 |
|
73 | protocol.USERNAME_MASK = 0x80
|
74 | protocol.PASSWORD_MASK = 0x40
|
75 | protocol.WILL_RETAIN_MASK = 0x20
|
76 | protocol.WILL_QOS_MASK = 0x18
|
77 | protocol.WILL_QOS_SHIFT = 3
|
78 | protocol.WILL_FLAG_MASK = 0x04
|
79 | protocol.CLEAN_SESSION_MASK = 0x02
|
80 | protocol.CONNECT_HEADER = Buffer.from([protocol.codes.connect << protocol.CMD_SHIFT])
|
81 |
|
82 |
|
83 | protocol.properties = {
|
84 | sessionExpiryInterval: 17,
|
85 | willDelayInterval: 24,
|
86 | receiveMaximum: 33,
|
87 | maximumPacketSize: 39,
|
88 | topicAliasMaximum: 34,
|
89 | requestResponseInformation: 25,
|
90 | requestProblemInformation: 23,
|
91 | userProperties: 38,
|
92 | authenticationMethod: 21,
|
93 | authenticationData: 22,
|
94 | payloadFormatIndicator: 1,
|
95 | messageExpiryInterval: 2,
|
96 | contentType: 3,
|
97 | responseTopic: 8,
|
98 | correlationData: 9,
|
99 | maximumQoS: 36,
|
100 | retainAvailable: 37,
|
101 | assignedClientIdentifier: 18,
|
102 | reasonString: 31,
|
103 | wildcardSubscriptionAvailable: 40,
|
104 | subscriptionIdentifiersAvailable: 41,
|
105 | sharedSubscriptionAvailable: 42,
|
106 | serverKeepAlive: 19,
|
107 | responseInformation: 26,
|
108 | serverReference: 28,
|
109 | topicAlias: 35,
|
110 | subscriptionIdentifier: 11
|
111 | }
|
112 | protocol.propertiesCodes = {}
|
113 | for (const prop in protocol.properties) {
|
114 | const id = protocol.properties[prop]
|
115 | protocol.propertiesCodes[id] = prop
|
116 | }
|
117 | protocol.propertiesTypes = {
|
118 | sessionExpiryInterval: 'int32',
|
119 | willDelayInterval: 'int32',
|
120 | receiveMaximum: 'int16',
|
121 | maximumPacketSize: 'int32',
|
122 | topicAliasMaximum: 'int16',
|
123 | requestResponseInformation: 'byte',
|
124 | requestProblemInformation: 'byte',
|
125 | userProperties: 'pair',
|
126 | authenticationMethod: 'string',
|
127 | authenticationData: 'binary',
|
128 | payloadFormatIndicator: 'byte',
|
129 | messageExpiryInterval: 'int32',
|
130 | contentType: 'string',
|
131 | responseTopic: 'string',
|
132 | correlationData: 'binary',
|
133 | maximumQoS: 'int8',
|
134 | retainAvailable: 'byte',
|
135 | assignedClientIdentifier: 'string',
|
136 | reasonString: 'string',
|
137 | wildcardSubscriptionAvailable: 'byte',
|
138 | subscriptionIdentifiersAvailable: 'byte',
|
139 | sharedSubscriptionAvailable: 'byte',
|
140 | serverKeepAlive: 'int16',
|
141 | responseInformation: 'string',
|
142 | serverReference: 'string',
|
143 | topicAlias: 'int16',
|
144 | subscriptionIdentifier: 'var'
|
145 | }
|
146 |
|
147 | function genHeader (type) {
|
148 | return [0, 1, 2].map(qos => {
|
149 | return [0, 1].map(dup => {
|
150 | return [0, 1].map(retain => {
|
151 | const buf = Buffer.alloc(1)
|
152 | buf.writeUInt8(
|
153 | protocol.codes[type] << protocol.CMD_SHIFT |
|
154 | (dup ? protocol.DUP_MASK : 0) |
|
155 | qos << protocol.QOS_SHIFT | retain, 0, true)
|
156 | return buf
|
157 | })
|
158 | })
|
159 | })
|
160 | }
|
161 |
|
162 |
|
163 | protocol.PUBLISH_HEADER = genHeader('publish')
|
164 |
|
165 |
|
166 | protocol.SUBSCRIBE_HEADER = genHeader('subscribe')
|
167 | protocol.SUBSCRIBE_OPTIONS_QOS_MASK = 0x03
|
168 | protocol.SUBSCRIBE_OPTIONS_NL_MASK = 0x01
|
169 | protocol.SUBSCRIBE_OPTIONS_NL_SHIFT = 2
|
170 | protocol.SUBSCRIBE_OPTIONS_RAP_MASK = 0x01
|
171 | protocol.SUBSCRIBE_OPTIONS_RAP_SHIFT = 3
|
172 | protocol.SUBSCRIBE_OPTIONS_RH_MASK = 0x03
|
173 | protocol.SUBSCRIBE_OPTIONS_RH_SHIFT = 4
|
174 | protocol.SUBSCRIBE_OPTIONS_RH = [0x00, 0x10, 0x20]
|
175 | protocol.SUBSCRIBE_OPTIONS_NL = 0x04
|
176 | protocol.SUBSCRIBE_OPTIONS_RAP = 0x08
|
177 | protocol.SUBSCRIBE_OPTIONS_QOS = [0x00, 0x01, 0x02]
|
178 |
|
179 |
|
180 | protocol.UNSUBSCRIBE_HEADER = genHeader('unsubscribe')
|
181 |
|
182 |
|
183 | protocol.ACKS = {
|
184 | unsuback: genHeader('unsuback'),
|
185 | puback: genHeader('puback'),
|
186 | pubcomp: genHeader('pubcomp'),
|
187 | pubrel: genHeader('pubrel'),
|
188 | pubrec: genHeader('pubrec')
|
189 | }
|
190 |
|
191 | protocol.SUBACK_HEADER = Buffer.from([protocol.codes.suback << protocol.CMD_SHIFT])
|
192 |
|
193 |
|
194 | protocol.VERSION3 = Buffer.from([3])
|
195 | protocol.VERSION4 = Buffer.from([4])
|
196 | protocol.VERSION5 = Buffer.from([5])
|
197 | protocol.VERSION131 = Buffer.from([131])
|
198 | protocol.VERSION132 = Buffer.from([132])
|
199 |
|
200 |
|
201 | protocol.QOS = [0, 1, 2].map(qos => {
|
202 | return Buffer.from([qos])
|
203 | })
|
204 |
|
205 |
|
206 | protocol.EMPTY = {
|
207 | pingreq: Buffer.from([protocol.codes.pingreq << 4, 0]),
|
208 | pingresp: Buffer.from([protocol.codes.pingresp << 4, 0]),
|
209 | disconnect: Buffer.from([protocol.codes.disconnect << 4, 0])
|
210 | }
|
211 |
|
212 | protocol.MQTT5_PUBACK_PUBREC_CODES = {
|
213 | 0x00: 'Success',
|
214 | 0x10: 'No matching subscribers',
|
215 | 0x80: 'Unspecified error',
|
216 | 0x83: 'Implementation specific error',
|
217 | 0x87: 'Not authorized',
|
218 | 0x90: 'Topic Name invalid',
|
219 | 0x91: 'Packet identifier in use',
|
220 | 0x97: 'Quota exceeded',
|
221 | 0x99: 'Payload format invalid'
|
222 | }
|
223 |
|
224 | protocol.MQTT5_PUBREL_PUBCOMP_CODES = {
|
225 | 0x00: 'Success',
|
226 | 0x92: 'Packet Identifier not found'
|
227 | }
|
228 |
|
229 | protocol.MQTT5_SUBACK_CODES = {
|
230 | 0x00: 'Granted QoS 0',
|
231 | 0x01: 'Granted QoS 1',
|
232 | 0x02: 'Granted QoS 2',
|
233 | 0x80: 'Unspecified error',
|
234 | 0x83: 'Implementation specific error',
|
235 | 0x87: 'Not authorized',
|
236 | 0x8F: 'Topic Filter invalid',
|
237 | 0x91: 'Packet Identifier in use',
|
238 | 0x97: 'Quota exceeded',
|
239 | 0x9E: 'Shared Subscriptions not supported',
|
240 | 0xA1: 'Subscription Identifiers not supported',
|
241 | 0xA2: 'Wildcard Subscriptions not supported'
|
242 | }
|
243 |
|
244 | protocol.MQTT5_UNSUBACK_CODES = {
|
245 | 0x00: 'Success',
|
246 | 0x11: 'No subscription existed',
|
247 | 0x80: 'Unspecified error',
|
248 | 0x83: 'Implementation specific error',
|
249 | 0x87: 'Not authorized',
|
250 | 0x8F: 'Topic Filter invalid',
|
251 | 0x91: 'Packet Identifier in use'
|
252 | }
|
253 |
|
254 | protocol.MQTT5_DISCONNECT_CODES = {
|
255 | 0x00: 'Normal disconnection',
|
256 | 0x04: 'Disconnect with Will Message',
|
257 | 0x80: 'Unspecified error',
|
258 | 0x81: 'Malformed Packet',
|
259 | 0x82: 'Protocol Error',
|
260 | 0x83: 'Implementation specific error',
|
261 | 0x87: 'Not authorized',
|
262 | 0x89: 'Server busy',
|
263 | 0x8B: 'Server shutting down',
|
264 | 0x8D: 'Keep Alive timeout',
|
265 | 0x8E: 'Session taken over',
|
266 | 0x8F: 'Topic Filter invalid',
|
267 | 0x90: 'Topic Name invalid',
|
268 | 0x93: 'Receive Maximum exceeded',
|
269 | 0x94: 'Topic Alias invalid',
|
270 | 0x95: 'Packet too large',
|
271 | 0x96: 'Message rate too high',
|
272 | 0x97: 'Quota exceeded',
|
273 | 0x98: 'Administrative action',
|
274 | 0x99: 'Payload format invalid',
|
275 | 0x9A: 'Retain not supported',
|
276 | 0x9B: 'QoS not supported',
|
277 | 0x9C: 'Use another server',
|
278 | 0x9D: 'Server moved',
|
279 | 0x9E: 'Shared Subscriptions not supported',
|
280 | 0x9F: 'Connection rate exceeded',
|
281 | 0xA0: 'Maximum connect time',
|
282 | 0xA1: 'Subscription Identifiers not supported',
|
283 | 0xA2: 'Wildcard Subscriptions not supported'
|
284 | }
|
285 |
|
286 | protocol.MQTT5_AUTH_CODES = {
|
287 | 0x00: 'Success',
|
288 | 0x18: 'Continue authentication',
|
289 | 0x19: 'Re-authenticate'
|
290 | }
|