/src/wolfmqtt/src/mqtt_client.c
Line | Count | Source |
1 | | /* mqtt_client.c |
2 | | * |
3 | | * Copyright (C) 2006-2026 wolfSSL Inc. |
4 | | * |
5 | | * This file is part of wolfMQTT. |
6 | | * |
7 | | * wolfMQTT is free software; you can redistribute it and/or modify |
8 | | * it under the terms of the GNU General Public License as published by |
9 | | * the Free Software Foundation; either version 3 of the License, or |
10 | | * (at your option) any later version. |
11 | | * |
12 | | * wolfMQTT is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU General Public License |
18 | | * along with this program; if not, write to the Free Software |
19 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA |
20 | | */ |
21 | | |
22 | | /* Include the autoconf generated config.h */ |
23 | | #ifdef HAVE_CONFIG_H |
24 | | #include <config.h> |
25 | | #endif |
26 | | |
27 | | #include "wolfmqtt/mqtt_client.h" |
28 | | |
29 | | /* Secure memory zeroing - uses volatile pointer to prevent the compiler |
30 | | * from optimizing away the stores (dead-store elimination). |
31 | | * Declared WOLFMQTT_LOCAL in mqtt_client.h so the MQTT-SN client can reuse it |
32 | | * (see SN_WillMessage) via the shared CLIENT_FORCE_ZERO macro. */ |
33 | | WOLFMQTT_LOCAL void MqttClient_ForceZero(void* mem, word32 len) |
34 | 8.22k | { |
35 | 8.22k | volatile byte* p = (volatile byte*)mem; |
36 | 8.22k | word32 i; |
37 | 15.8M | for (i = 0; i < len; i++) { |
38 | 15.8M | p[i] = 0; |
39 | 15.8M | } |
40 | 8.22k | } |
41 | | |
42 | | /* DOCUMENTED BUILD OPTIONS: |
43 | | * |
44 | | * WOLFMQTT_MULTITHREAD: Enables multi-thread support with mutex protection on |
45 | | * client struct, write and read. When a pending response is needed its added |
46 | | * to a linked list and if another thread reads the expected response it is |
47 | | * flagged, so the other thread knows it completed. |
48 | | * |
49 | | * WOLFMQTT_NONBLOCK: Enabled transport support for returning WANT READ/WRITE, |
50 | | * which becomes WOLFMQTT_CODE_CONTINUE. This prevents blocking if the |
51 | | * transport (socket) has no data. |
52 | | * |
53 | | * WOLFMQTT_V5: Enables MQTT v5.0 support |
54 | | * |
55 | | * WOLFMQTT_ALLOW_NODATA_UNLOCK: Used with multi-threading and non-blocking to |
56 | | * allow unlock if no data was sent/received. Note the TLS stack typically |
57 | | * requires an attempt to write to continue with same write, not different. |
58 | | * By default if we attempt a write we keep the mutex locked and return |
59 | | * MQTT_CODE_CONTINUE |
60 | | * |
61 | | * WOLFMQTT_USER_THREADING: Allows custom mutex functions to be defined by the |
62 | | * user. Example: wm_SemInit |
63 | | * |
64 | | * WOLFMQTT_DEBUG_CLIENT: Enables verbose PRINTF for the client code. |
65 | | */ |
66 | | |
67 | | |
68 | | /* Private functions */ |
69 | | |
70 | | /* forward declarations */ |
71 | | static int MqttClient_Publish_ReadPayload(MqttClient* client, |
72 | | MqttPublish* publish, int timeout_ms); |
73 | | #if !defined(WOLFMQTT_MULTITHREAD) && !defined(WOLFMQTT_NONBLOCK) |
74 | | static int MqttClient_CancelMessage(MqttClient *client, MqttObject* msg); |
75 | | #endif |
76 | | |
77 | | |
78 | | #ifdef WOLFMQTT_MULTITHREAD |
79 | | |
80 | | #ifdef WOLFMQTT_USER_THREADING |
81 | | |
82 | | /* User will supply their own semaphore functions. |
83 | | * int wm_SemInit(wm_Sem *s) |
84 | | * int wm_SemFree(wm_Sem *s) |
85 | | * int wm_SemLock(wm_Sem *s) |
86 | | * int wm_SemUnlock(wm_Sem *s) |
87 | | */ |
88 | | |
89 | | #elif defined(__MACH__) |
90 | | |
91 | | /* Apple style dispatch semaphore */ |
92 | | int wm_SemInit(wm_Sem *s) { |
93 | | /* dispatch_release() fails hard, with Trace/BPT trap signal, if the |
94 | | * sem's internal count is less than the value passed in with |
95 | | * dispatch_semaphore_create(). work around this by initializing |
96 | | * with 0, then incrementing it afterwards. |
97 | | */ |
98 | | s->sem = dispatch_semaphore_create(0); |
99 | | if (s->sem == NULL) |
100 | | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MEMORY); |
101 | | if (dispatch_semaphore_signal(s->sem) < 0) { |
102 | | dispatch_release(s->sem); |
103 | | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_SYSTEM); |
104 | | } |
105 | | |
106 | | return 0; |
107 | | } |
108 | | int wm_SemFree(wm_Sem *s) { |
109 | | if ((s == NULL) || |
110 | | (s->sem == NULL)) |
111 | | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
112 | | dispatch_release(s->sem); |
113 | | s->sem = NULL; |
114 | | return 0; |
115 | | } |
116 | | int wm_SemLock(wm_Sem *s) { |
117 | | dispatch_semaphore_wait(s->sem, DISPATCH_TIME_FOREVER); |
118 | | return 0; |
119 | | } |
120 | | int wm_SemUnlock(wm_Sem *s){ |
121 | | dispatch_semaphore_signal(s->sem); |
122 | | return 0; |
123 | | } |
124 | | #elif defined(WOLFMQTT_POSIX_SEMAPHORES) |
125 | | /* Posix style semaphore */ |
126 | | int wm_SemInit(wm_Sem *s) { |
127 | | #ifndef WOLFMQTT_NO_COND_SIGNAL |
128 | | s->lockCount = 0; |
129 | | pthread_cond_init(&s->cond, NULL); |
130 | | #endif |
131 | | pthread_mutex_init(&s->mutex, NULL); |
132 | | return 0; |
133 | | } |
134 | | int wm_SemFree(wm_Sem *s) { |
135 | | pthread_mutex_destroy(&s->mutex); |
136 | | #ifndef WOLFMQTT_NO_COND_SIGNAL |
137 | | pthread_cond_destroy(&s->cond); |
138 | | #endif |
139 | | return 0; |
140 | | } |
141 | | int wm_SemLock(wm_Sem *s) { |
142 | | pthread_mutex_lock(&s->mutex); |
143 | | #ifndef WOLFMQTT_NO_COND_SIGNAL |
144 | | while (s->lockCount > 0) |
145 | | pthread_cond_wait(&s->cond, &s->mutex); |
146 | | s->lockCount++; |
147 | | pthread_mutex_unlock(&s->mutex); |
148 | | #endif |
149 | | return 0; |
150 | | } |
151 | | int wm_SemUnlock(wm_Sem *s) { |
152 | | #ifndef WOLFMQTT_NO_COND_SIGNAL |
153 | | pthread_mutex_lock(&s->mutex); |
154 | | if (s->lockCount > 0) { |
155 | | s->lockCount--; |
156 | | pthread_cond_signal(&s->cond); |
157 | | } |
158 | | #endif |
159 | | pthread_mutex_unlock(&s->mutex); |
160 | | return 0; |
161 | | } |
162 | | #elif defined(FREERTOS) |
163 | | /* FreeRTOS binary semaphore */ |
164 | | int wm_SemInit(wm_Sem *s) { |
165 | | *s = xSemaphoreCreateBinary(); |
166 | | xSemaphoreGive(*s); |
167 | | return 0; |
168 | | } |
169 | | int wm_SemFree(wm_Sem *s) { |
170 | | vSemaphoreDelete(*s); |
171 | | *s = NULL; |
172 | | return 0; |
173 | | } |
174 | | int wm_SemLock(wm_Sem *s) { |
175 | | xSemaphoreTake(*s, portMAX_DELAY); |
176 | | return 0; |
177 | | } |
178 | | int wm_SemUnlock(wm_Sem *s) { |
179 | | xSemaphoreGive(*s); |
180 | | return 0; |
181 | | } |
182 | | #elif defined(USE_WINDOWS_API) |
183 | | /* Windows semaphore object */ |
184 | | int wm_SemInit(wm_Sem *s) { |
185 | | *s = CreateSemaphoreW( NULL, 1, 1, NULL); |
186 | | return 0; |
187 | | } |
188 | | int wm_SemFree(wm_Sem *s) { |
189 | | CloseHandle(*s); |
190 | | *s = NULL; |
191 | | return 0; |
192 | | } |
193 | | int wm_SemLock(wm_Sem *s) { |
194 | | WaitForSingleObject(*s, INFINITE); |
195 | | return 0; |
196 | | } |
197 | | int wm_SemUnlock(wm_Sem *s) { |
198 | | ReleaseSemaphore(*s, 1, NULL); |
199 | | return 0; |
200 | | } |
201 | | |
202 | | #elif defined(THREADX) |
203 | | /* ThreadX semaphore */ |
204 | | int wm_SemInit(wm_Sem *s) { |
205 | | if (tx_semaphore_create(s, NULL, 1) != TX_SUCCESS) { |
206 | | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_SYSTEM); |
207 | | } |
208 | | return 0; |
209 | | } |
210 | | int wm_SemFree(wm_Sem *s) { |
211 | | if (tx_semaphore_delete(s) != TX_SUCCESS) { |
212 | | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_SYSTEM); |
213 | | } |
214 | | return 0; |
215 | | } |
216 | | |
217 | | int wm_SemLock(wm_Sem *s) { |
218 | | UINT semstatus = tx_semaphore_get(s, TX_WAIT_FOREVER); |
219 | | if (semstatus != TX_SUCCESS) { |
220 | | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_SYSTEM); |
221 | | } |
222 | | return 0; |
223 | | } |
224 | | int wm_SemUnlock(wm_Sem *s) { |
225 | | if (tx_semaphore_put(s) != TX_SUCCESS) { |
226 | | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_SYSTEM); |
227 | | } |
228 | | return 0; |
229 | | } |
230 | | #endif /* MUTEX */ |
231 | | #endif /* WOLFMQTT_MULTITHREAD */ |
232 | | |
233 | | static int MqttWriteStart(MqttClient* client, MqttMsgStat* stat) |
234 | 11.6k | { |
235 | 11.6k | int rc = MQTT_CODE_SUCCESS; |
236 | | |
237 | 11.6k | #if defined(WOLFMQTT_DEBUG_CLIENT) || !defined(WOLFMQTT_ALLOW_NODATA_UNLOCK) |
238 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
239 | | if (stat->isWriteActive) { |
240 | | MQTT_TRACE_MSG("Warning, send already locked!"); |
241 | | rc = MQTT_CODE_ERROR_SYSTEM; |
242 | | } |
243 | | #endif |
244 | 11.6k | #ifndef WOLFMQTT_ALLOW_NODATA_UNLOCK |
245 | | /* detect if a write is already in progress */ |
246 | | #ifdef WOLFMQTT_MULTITHREAD |
247 | | if (wm_SemLock(&client->lockClient) == 0) |
248 | | #endif |
249 | 11.6k | { |
250 | 11.6k | if (client->write.isActive) { |
251 | 0 | MQTT_TRACE_MSG("Partial write in progress!"); |
252 | 0 | rc = MQTT_CODE_CONTINUE; /* can't write yet */ |
253 | 0 | } |
254 | | #ifdef WOLFMQTT_MULTITHREAD |
255 | | wm_SemUnlock(&client->lockClient); |
256 | | #endif |
257 | 11.6k | } |
258 | 11.6k | #endif /* WOLFMQTT_ALLOW_NODATA_UNLOCK */ |
259 | 11.6k | if (rc != MQTT_CODE_SUCCESS) { |
260 | 0 | return rc; |
261 | 0 | } |
262 | 11.6k | #endif |
263 | | |
264 | | #ifdef WOLFMQTT_MULTITHREAD |
265 | | rc = wm_SemLock(&client->lockSend); |
266 | | #endif |
267 | 11.6k | if (rc == MQTT_CODE_SUCCESS) { |
268 | 11.6k | stat->isWriteActive = 1; |
269 | | |
270 | | #ifdef WOLFMQTT_MULTITHREAD |
271 | | if (wm_SemLock(&client->lockClient) == 0) |
272 | | #endif |
273 | 11.6k | { |
274 | 11.6k | client->write.isActive = 1; |
275 | | #ifdef WOLFMQTT_MULTITHREAD |
276 | | wm_SemUnlock(&client->lockClient); |
277 | | #endif |
278 | 11.6k | } |
279 | | |
280 | 11.6k | MQTT_TRACE_MSG("lockSend"); |
281 | 11.6k | } |
282 | | |
283 | 11.6k | return rc; |
284 | 11.6k | } |
285 | | static void MqttWriteStop(MqttClient* client, MqttMsgStat* stat) |
286 | 11.7k | { |
287 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
288 | | if (!stat->isWriteActive) { |
289 | | MQTT_TRACE_MSG("Warning, send not locked!"); |
290 | | return; |
291 | | } |
292 | | #endif |
293 | | |
294 | | #ifdef WOLFMQTT_MULTITHREAD |
295 | | if (wm_SemLock(&client->lockClient) == 0) |
296 | | #endif |
297 | 11.7k | { |
298 | | /* reset write */ |
299 | 11.7k | XMEMSET(&client->write, 0, sizeof(client->write)); |
300 | | #ifdef WOLFMQTT_MULTITHREAD |
301 | | wm_SemUnlock(&client->lockClient); |
302 | | #endif |
303 | 11.7k | } |
304 | | |
305 | 11.7k | if (stat->isWriteActive) { |
306 | 11.6k | MQTT_TRACE_MSG("unlockSend"); |
307 | 11.6k | stat->isWriteActive = 0; |
308 | | #ifdef WOLFMQTT_MULTITHREAD |
309 | | wm_SemUnlock(&client->lockSend); |
310 | | #endif |
311 | 11.6k | } |
312 | 11.7k | } |
313 | | |
314 | | WOLFMQTT_LOCAL int MqttReadStart(MqttClient* client, MqttMsgStat* stat) |
315 | 13.4k | { |
316 | 13.4k | int rc = MQTT_CODE_SUCCESS; |
317 | | |
318 | 13.4k | #if defined(WOLFMQTT_DEBUG_CLIENT) || !defined(WOLFMQTT_ALLOW_NODATA_UNLOCK) |
319 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
320 | | if (stat->isReadActive) { |
321 | | MQTT_TRACE_MSG("Warning, recv already locked!"); |
322 | | rc = MQTT_CODE_ERROR_SYSTEM; |
323 | | } |
324 | | #endif /* WOLFMQTT_DEBUG_CLIENT */ |
325 | 13.4k | #ifndef WOLFMQTT_ALLOW_NODATA_UNLOCK |
326 | | /* detect if a read is already in progress */ |
327 | | #ifdef WOLFMQTT_MULTITHREAD |
328 | | if (wm_SemLock(&client->lockClient) == 0) |
329 | | #endif |
330 | 13.4k | { |
331 | 13.4k | if (client->read.isActive) { |
332 | 0 | MQTT_TRACE_MSG("Partial read in progress!"); |
333 | 0 | rc = MQTT_CODE_CONTINUE; /* can't read yet */ |
334 | 0 | } |
335 | | #ifdef WOLFMQTT_MULTITHREAD |
336 | | wm_SemUnlock(&client->lockClient); |
337 | | #endif |
338 | 13.4k | } |
339 | 13.4k | #endif /* WOLFMQTT_ALLOW_NODATA_UNLOCK */ |
340 | 13.4k | if (rc != MQTT_CODE_SUCCESS) { |
341 | 0 | return rc; |
342 | 0 | } |
343 | 13.4k | #endif /* WOLFMQTT_DEBUG_CLIENT || !WOLFMQTT_ALLOW_NODATA_UNLOCK */ |
344 | | |
345 | | #ifdef WOLFMQTT_MULTITHREAD |
346 | | rc = wm_SemLock(&client->lockRecv); |
347 | | #endif |
348 | 13.4k | if (rc == MQTT_CODE_SUCCESS) { |
349 | 13.4k | stat->isReadActive = 1; |
350 | | |
351 | | #ifdef WOLFMQTT_MULTITHREAD |
352 | | if (wm_SemLock(&client->lockClient) == 0) |
353 | | #endif |
354 | 13.4k | { |
355 | | /* mark read active */ |
356 | 13.4k | client->read.isActive = 1; |
357 | | |
358 | | /* reset the packet state used by MqttPacket_Read */ |
359 | 13.4k | client->packet.stat = MQTT_PK_BEGIN; |
360 | | |
361 | | #ifdef WOLFMQTT_MULTITHREAD |
362 | | wm_SemUnlock(&client->lockClient); |
363 | | #endif |
364 | 13.4k | } |
365 | | |
366 | 13.4k | MQTT_TRACE_MSG("lockRecv"); |
367 | 13.4k | } |
368 | | |
369 | 13.4k | return rc; |
370 | 13.4k | } |
371 | | WOLFMQTT_LOCAL void MqttReadStop(MqttClient* client, MqttMsgStat* stat) |
372 | 28.0k | { |
373 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
374 | | if (!stat->isReadActive) { |
375 | | MQTT_TRACE_MSG("Warning, recv not locked!"); |
376 | | return; |
377 | | } |
378 | | #endif |
379 | | |
380 | | #ifdef WOLFMQTT_MULTITHREAD |
381 | | if (wm_SemLock(&client->lockClient) == 0) |
382 | | #endif |
383 | 28.0k | { |
384 | | /* reset read */ |
385 | 28.0k | XMEMSET(&client->read, 0, sizeof(client->read)); |
386 | | #ifdef WOLFMQTT_MULTITHREAD |
387 | | wm_SemUnlock(&client->lockClient); |
388 | | #endif |
389 | 28.0k | } |
390 | | |
391 | 28.0k | if (stat->isReadActive) { |
392 | 13.4k | MQTT_TRACE_MSG("unlockRecv"); |
393 | 13.4k | stat->isReadActive = 0; |
394 | | #ifdef WOLFMQTT_MULTITHREAD |
395 | | wm_SemUnlock(&client->lockRecv); |
396 | | #endif |
397 | 13.4k | } |
398 | 28.0k | } |
399 | | |
400 | | #ifdef WOLFMQTT_MULTITHREAD |
401 | | |
402 | | /* These RespList functions assume caller has locked client->lockClient mutex */ |
403 | | int MqttClient_RespList_Add(MqttClient *client, |
404 | | MqttPacketType packet_type, word16 packet_id, MqttPendResp *newResp, |
405 | | void *packet_obj) |
406 | | { |
407 | | MqttPendResp *tmpResp; |
408 | | |
409 | | if (client == NULL) |
410 | | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
411 | | |
412 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
413 | | PRINTF("PendResp Add: %p, Type %s (%d), ID %d", |
414 | | newResp, MqttPacket_TypeDesc(packet_type), packet_type, packet_id); |
415 | | #endif |
416 | | |
417 | | /* Verify newResp is not already in the list, and enforce MQTT Packet |
418 | | * Identifier in-use uniqueness: the spec (3.1.1 section 2.3.1, 5.0 section 2.2.1) |
419 | | * requires a new QoS-related Control Packet to use a Packet Identifier |
420 | | * that is not currently in use. The identifier becomes reusable only |
421 | | * after the corresponding acknowledgement flow completes and the entry |
422 | | * is removed from this list. A packet_id of 0 is used for packet types |
423 | | * that do not carry a Packet Identifier (CONNECT_ACK, PING_RESP, AUTH) |
424 | | * and is excluded from the collision check. */ |
425 | | for (tmpResp = client->firstPendResp; |
426 | | tmpResp != NULL; |
427 | | tmpResp = tmpResp->next) |
428 | | { |
429 | | if (tmpResp == newResp) { |
430 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
431 | | PRINTF("Pending Response already in list!"); |
432 | | #endif |
433 | | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
434 | | } |
435 | | if (packet_id != 0 && tmpResp->packet_id == packet_id) { |
436 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
437 | | PRINTF("Pending Response packet_id %d already in use " |
438 | | "(existing type %s (%d), new type %s (%d))", |
439 | | packet_id, |
440 | | MqttPacket_TypeDesc(tmpResp->packet_type), |
441 | | tmpResp->packet_type, |
442 | | MqttPacket_TypeDesc(packet_type), packet_type); |
443 | | #endif |
444 | | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PACKET_ID); |
445 | | } |
446 | | } |
447 | | |
448 | | /* Initialize new response */ |
449 | | XMEMSET(newResp, 0, sizeof(MqttPendResp)); |
450 | | newResp->packet_id = packet_id; |
451 | | newResp->packet_type = packet_type; |
452 | | /* opaque pointer to struct based on type */ |
453 | | newResp->packet_obj = packet_obj; |
454 | | |
455 | | if (client->lastPendResp == NULL) { |
456 | | /* This is the only list item */ |
457 | | client->firstPendResp = newResp; |
458 | | client->lastPendResp = newResp; |
459 | | } |
460 | | else { |
461 | | /* Append to end of list */ |
462 | | newResp->prev = client->lastPendResp; |
463 | | client->lastPendResp->next = newResp; |
464 | | client->lastPendResp = newResp; |
465 | | } |
466 | | return MQTT_CODE_SUCCESS; |
467 | | } |
468 | | |
469 | | void MqttClient_RespList_Remove(MqttClient *client, MqttPendResp *rmResp) |
470 | | { |
471 | | MqttPendResp *tmpResp; |
472 | | |
473 | | if (client == NULL) |
474 | | return; |
475 | | |
476 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
477 | | PRINTF("PendResp Remove: %p", rmResp); |
478 | | #endif |
479 | | |
480 | | /* Find the response entry */ |
481 | | for (tmpResp = client->firstPendResp; |
482 | | tmpResp != NULL; |
483 | | tmpResp = tmpResp->next) |
484 | | { |
485 | | if (tmpResp == rmResp) { |
486 | | break; |
487 | | } |
488 | | } |
489 | | if (tmpResp) { |
490 | | /* Fix up the first and last pointers */ |
491 | | if (client->firstPendResp == tmpResp) { |
492 | | client->firstPendResp = tmpResp->next; |
493 | | } |
494 | | if (client->lastPendResp == tmpResp) { |
495 | | client->lastPendResp = tmpResp->prev; |
496 | | } |
497 | | |
498 | | /* Remove the entry from the list */ |
499 | | if (tmpResp->next != NULL) { |
500 | | tmpResp->next->prev = tmpResp->prev; |
501 | | } |
502 | | if (tmpResp->prev != NULL) { |
503 | | tmpResp->prev->next = tmpResp->next; |
504 | | } |
505 | | } |
506 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
507 | | else { |
508 | | PRINTF("\tPendResp not found"); |
509 | | } |
510 | | #endif |
511 | | } |
512 | | |
513 | | /* return codes: 0=not found, 1=found */ |
514 | | int MqttClient_RespList_Find(MqttClient *client, |
515 | | MqttPacketType packet_type, word16 packet_id, MqttPendResp **retResp) |
516 | | { |
517 | | int rc = 0; |
518 | | MqttPendResp *tmpResp; |
519 | | |
520 | | if (client == NULL) |
521 | | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
522 | | |
523 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
524 | | #ifdef WOLFMQTT_NONBLOCK |
525 | | if (client->lastRc != MQTT_CODE_CONTINUE) |
526 | | #endif |
527 | | { |
528 | | PRINTF("PendResp Find: Type %s (%d), ID %d", |
529 | | MqttPacket_TypeDesc(packet_type), packet_type, packet_id); |
530 | | } |
531 | | #endif |
532 | | |
533 | | if (retResp) |
534 | | *retResp = NULL; /* clear */ |
535 | | |
536 | | /* Find pending response entry */ |
537 | | for (tmpResp = client->firstPendResp; |
538 | | tmpResp != NULL; |
539 | | tmpResp = tmpResp->next) |
540 | | { |
541 | | if (packet_type == tmpResp->packet_type && |
542 | | (packet_id == tmpResp->packet_id)) |
543 | | { |
544 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
545 | | #if defined(WOLFMQTT_NONBLOCK) && defined(WOLFMQTT_DEBUG_CLIENT) |
546 | | if (client->lastRc != MQTT_CODE_CONTINUE) |
547 | | #endif |
548 | | { |
549 | | PRINTF("PendResp Found: %p, Type %s (%d), ID %d, InProc %d, Done %d", |
550 | | tmpResp, MqttPacket_TypeDesc(tmpResp->packet_type), |
551 | | tmpResp->packet_type, tmpResp->packet_id, |
552 | | tmpResp->packetProcessing, tmpResp->packetDone); |
553 | | } |
554 | | #endif |
555 | | |
556 | | if (retResp) |
557 | | *retResp = tmpResp; |
558 | | rc = 1; |
559 | | break; |
560 | | } |
561 | | } |
562 | | return rc; |
563 | | } |
564 | | #endif /* WOLFMQTT_MULTITHREAD */ |
565 | | |
566 | | #ifdef WOLFMQTT_V5 |
567 | | /* Populate client fields from CONNACK server properties so that the |
568 | | * publish/packet-size guards are effective without requiring the |
569 | | * application to register a property callback. */ |
570 | | static void Handle_ConnectAck_Props(MqttClient* client, MqttProp* props) |
571 | 319 | { |
572 | 319 | MqttProp* prop; |
573 | | |
574 | 319 | for (prop = props; prop != NULL; prop = prop->next) { |
575 | 0 | if (prop->type == MQTT_PROP_MAX_QOS) { |
576 | | /* MQTT v5 [3.1.2.11.6]: only 0 or 1 are legal. Clamp a |
577 | | * non-conforming broker value, then narrow against this |
578 | | * build's WOLFMQTT_MAX_QOS so client-side publish guards |
579 | | * remain meaningful. */ |
580 | 0 | byte adv = (prop->data_byte <= MQTT_QOS_1) ? |
581 | 0 | prop->data_byte : MQTT_QOS_1; |
582 | 0 | if (adv > WOLFMQTT_MAX_QOS) { |
583 | 0 | adv = (byte)WOLFMQTT_MAX_QOS; |
584 | 0 | } |
585 | 0 | client->max_qos = adv; |
586 | 0 | } |
587 | 0 | else if (prop->type == MQTT_PROP_RETAIN_AVAIL) { |
588 | | /* MQTT v5 [3.1.2.11.5]: only 0 or 1 are legal. */ |
589 | 0 | client->retain_avail = (prop->data_byte <= 1) ? |
590 | 0 | prop->data_byte : 1; |
591 | 0 | } |
592 | 0 | else if (prop->type == MQTT_PROP_MAX_PACKET_SZ) { |
593 | 0 | if ((prop->data_int > 0) && |
594 | 0 | (prop->data_int <= MQTT_PACKET_SZ_MAX)) { |
595 | | /* Honor the smaller of the client's existing cap |
596 | | * (0 means unset) and the server's limit. */ |
597 | 0 | if ((client->packet_sz_max == 0) || |
598 | 0 | (prop->data_int < client->packet_sz_max)) { |
599 | 0 | client->packet_sz_max = prop->data_int; |
600 | 0 | } |
601 | 0 | } |
602 | 0 | } |
603 | 0 | #ifndef WOLFMQTT_NO_TIME |
604 | 0 | else if (prop->type == MQTT_PROP_SERVER_KEEP_ALIVE) { |
605 | | /* MQTT v5 [3.1.2.11.2]: when the broker returns a Server Keep |
606 | | * Alive, the client MUST use it in place of the value it sent, and |
607 | | * a value of 0 disables keep-alive. The flag lets the arming logic |
608 | | * tell a server-provided 0 from an absent property. */ |
609 | 0 | client->keep_alive_sec = prop->data_short; |
610 | 0 | client->keep_alive_from_server = 1; |
611 | 0 | } |
612 | 0 | #endif |
613 | 0 | } |
614 | 319 | } |
615 | | |
616 | | static int Handle_Props(MqttClient* client, MqttProp* props, byte use_cb, |
617 | | byte free_props) |
618 | 18.5k | { |
619 | 18.5k | int rc = MQTT_CODE_SUCCESS; |
620 | | |
621 | | /* If no properties, just return */ |
622 | 18.5k | if (props != NULL) { |
623 | 1.60k | #ifdef WOLFMQTT_PROPERTY_CB |
624 | | /* Check for properties set by the server */ |
625 | 1.60k | if ((use_cb == 1) && (client->property_cb != NULL)) { |
626 | | /* capture error if returned */ |
627 | 0 | int rc_err = client->property_cb(client, props, |
628 | 0 | client->property_ctx); |
629 | 0 | if (rc_err < 0) { |
630 | 0 | rc = rc_err; |
631 | 0 | } |
632 | 0 | } |
633 | | #else |
634 | | (void)client; |
635 | | (void)use_cb; |
636 | | #endif |
637 | 1.60k | if (free_props) { |
638 | | /* Free the properties */ |
639 | 1.60k | MqttProps_Free(props); |
640 | 1.60k | } |
641 | 1.60k | } |
642 | 18.5k | return rc; |
643 | 18.5k | } |
644 | | #endif |
645 | | |
646 | | |
647 | | /* Returns length decoded or error (as negative) */ |
648 | | /*! \brief Take a received MQTT packet and try and decode it |
649 | | * \param client MQTT client context |
650 | | * \param rx_buf Incoming buffer data |
651 | | * \param rx_len Incoming buffer length |
652 | | * \param p_decode Opaque pointer to packet structure based on type |
653 | | * \param ppacket_type Decoded packet type |
654 | | * \param ppacket_qos Decoded QoS level |
655 | | * \param ppacket_id Decoded packet id |
656 | | * \param doProps True: Call Handle_Props to free prop struct |
657 | | |
658 | | * \return Returns length decoded or error (as negative) MQTT_CODE_ERROR_* |
659 | | (see enum MqttPacketResponseCodes) |
660 | | */ |
661 | | static int MqttClient_DecodePacket(MqttClient* client, byte* rx_buf, |
662 | | word32 rx_len, void *packet_obj, MqttPacketType* ppacket_type, |
663 | | MqttQoS* ppacket_qos, word16* ppacket_id, int doProps) |
664 | 25.0k | { |
665 | 25.0k | int rc = MQTT_CODE_SUCCESS; |
666 | 25.0k | MqttPacket* header; |
667 | 25.0k | MqttPacketType packet_type; |
668 | 25.0k | MqttQoS packet_qos; |
669 | 25.0k | word16 packet_id = 0; |
670 | | |
671 | | /* must have rx buffer with at least 2 byes for header */ |
672 | 25.0k | if (rx_buf == NULL || rx_len < MQTT_PACKET_HEADER_MIN_SIZE) { |
673 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
674 | 0 | } |
675 | | |
676 | | /* Decode header */ |
677 | 25.0k | header = (MqttPacket*)rx_buf; |
678 | 25.0k | packet_type = (MqttPacketType)MQTT_PACKET_TYPE_GET(header->type_flags); |
679 | 25.0k | if (ppacket_type) { |
680 | 25.0k | *ppacket_type = packet_type; |
681 | 25.0k | } |
682 | 25.0k | packet_qos = (MqttQoS)MQTT_PACKET_FLAGS_GET_QOS(header->type_flags); |
683 | 25.0k | if (ppacket_qos) { |
684 | 25.0k | *ppacket_qos = packet_qos; |
685 | 25.0k | } |
686 | | |
687 | | /* Decode packet specific data (if requested) */ |
688 | 25.0k | if (ppacket_id || packet_obj) { |
689 | 25.0k | switch (packet_type) { |
690 | 1.36k | case MQTT_PACKET_TYPE_CONNECT_ACK: |
691 | 1.36k | { |
692 | 1.36k | MqttConnectAck connect_ack, *p_connect_ack = &connect_ack; |
693 | 1.36k | if (packet_obj) { |
694 | 777 | p_connect_ack = (MqttConnectAck*)packet_obj; |
695 | 777 | } |
696 | 588 | else { |
697 | 588 | XMEMSET(p_connect_ack, 0, sizeof(MqttConnectAck)); |
698 | 588 | } |
699 | 1.36k | #ifdef WOLFMQTT_V5 |
700 | 1.36k | p_connect_ack->protocol_level = client->protocol_level; |
701 | 1.36k | #endif |
702 | 1.36k | rc = MqttDecode_ConnectAck(rx_buf, rx_len, p_connect_ack); |
703 | 1.36k | #ifdef WOLFMQTT_V5 |
704 | 1.36k | if (rc >= 0 && doProps) { |
705 | 1.00k | int tmp; |
706 | | /* Only latch server-supplied session limits when the broker |
707 | | * accepted the connection. A refused CONNACK must not |
708 | | * mutate long-lived MqttClient state. */ |
709 | 1.00k | if (p_connect_ack->return_code == |
710 | 1.00k | MQTT_CONNECT_ACK_CODE_ACCEPTED) { |
711 | 319 | Handle_ConnectAck_Props(client, p_connect_ack->props); |
712 | 319 | } |
713 | 1.00k | tmp = Handle_Props(client, p_connect_ack->props, |
714 | 1.00k | (packet_obj != NULL), 1); |
715 | 1.00k | p_connect_ack->props = NULL; |
716 | 1.00k | if (tmp != MQTT_CODE_SUCCESS) { |
717 | 0 | rc = tmp; |
718 | 0 | } |
719 | 1.00k | } |
720 | 1.36k | #endif |
721 | 1.36k | break; |
722 | 0 | } |
723 | 3.39k | case MQTT_PACKET_TYPE_PUBLISH: |
724 | 3.39k | { |
725 | 3.39k | MqttPublish publish, *p_publish; |
726 | 3.39k | if (packet_obj) { |
727 | 2.08k | p_publish = (MqttPublish*)packet_obj; |
728 | 2.08k | #ifdef WOLFMQTT_V5 |
729 | | /* setting the protocol level will enable parsing of the |
730 | | * properties. The properties are allocated from a list, |
731 | | * so only parse if we are using a return packet object */ |
732 | 2.08k | p_publish->protocol_level = client->protocol_level; |
733 | 2.08k | #endif |
734 | 2.08k | } |
735 | 1.30k | else { |
736 | 1.30k | p_publish = &publish; |
737 | 1.30k | XMEMSET(p_publish, 0, sizeof(MqttPublish)); |
738 | 1.30k | } |
739 | 3.39k | rc = MqttDecode_Publish(rx_buf, rx_len, p_publish); |
740 | 3.39k | if (rc >= 0) { |
741 | 2.21k | packet_id = p_publish->packet_id; |
742 | 2.21k | #ifdef WOLFMQTT_V5 |
743 | 2.21k | if (doProps) { |
744 | | /* Do not free property list here. It will be freed |
745 | | after the message callback. */ |
746 | 2.21k | int tmp = Handle_Props(client, p_publish->props, |
747 | 2.21k | (packet_obj != NULL), 0); |
748 | 2.21k | if (tmp != MQTT_CODE_SUCCESS) { |
749 | 0 | rc = tmp; |
750 | 0 | } |
751 | 2.21k | } |
752 | 2.21k | #endif |
753 | 2.21k | } |
754 | 3.39k | break; |
755 | 0 | } |
756 | 931 | case MQTT_PACKET_TYPE_PUBLISH_ACK: |
757 | 2.32k | case MQTT_PACKET_TYPE_PUBLISH_REC: |
758 | 3.77k | case MQTT_PACKET_TYPE_PUBLISH_REL: |
759 | 5.38k | case MQTT_PACKET_TYPE_PUBLISH_COMP: |
760 | 5.38k | { |
761 | 5.38k | MqttPublishResp publish_resp, *p_publish_resp = &publish_resp; |
762 | 5.38k | if (packet_obj) { |
763 | 2.75k | p_publish_resp = (MqttPublishResp*)packet_obj; |
764 | 2.75k | } |
765 | 2.63k | else { |
766 | 2.63k | XMEMSET(p_publish_resp, 0, sizeof(MqttPublishResp)); |
767 | 2.63k | } |
768 | | |
769 | 5.38k | #ifdef WOLFMQTT_V5 |
770 | 5.38k | p_publish_resp->protocol_level = client->protocol_level; |
771 | 5.38k | #endif |
772 | 5.38k | rc = MqttDecode_PublishResp(rx_buf, rx_len, packet_type, |
773 | 5.38k | p_publish_resp); |
774 | 5.38k | if (rc >= 0) { |
775 | 5.15k | packet_id = p_publish_resp->packet_id; |
776 | 5.15k | #ifdef WOLFMQTT_V5 |
777 | 5.15k | if (doProps) { |
778 | 5.15k | int tmp = Handle_Props(client, p_publish_resp->props, |
779 | 5.15k | (packet_obj != NULL), 1); |
780 | 5.15k | p_publish_resp->props = NULL; |
781 | 5.15k | if (tmp != MQTT_CODE_SUCCESS) { |
782 | 0 | rc = tmp; |
783 | 0 | } |
784 | 5.15k | } |
785 | 5.15k | #endif |
786 | 5.15k | } |
787 | 5.38k | break; |
788 | 3.77k | } |
789 | 1.86k | case MQTT_PACKET_TYPE_SUBSCRIBE_ACK: |
790 | 1.86k | { |
791 | 1.86k | MqttSubscribeAck subscribe_ack, *p_subscribe_ack = &subscribe_ack; |
792 | 1.86k | if (packet_obj) { |
793 | 1.02k | p_subscribe_ack = (MqttSubscribeAck*)packet_obj; |
794 | 1.02k | } |
795 | 839 | else { |
796 | 839 | XMEMSET(p_subscribe_ack, 0, sizeof(MqttSubscribeAck)); |
797 | 839 | } |
798 | 1.86k | #ifdef WOLFMQTT_V5 |
799 | 1.86k | p_subscribe_ack->protocol_level = client->protocol_level; |
800 | 1.86k | #endif |
801 | 1.86k | rc = MqttDecode_SubscribeAck(rx_buf, rx_len, p_subscribe_ack); |
802 | 1.86k | if (rc >= 0) { |
803 | 1.43k | packet_id = p_subscribe_ack->packet_id; |
804 | 1.43k | #ifdef WOLFMQTT_V5 |
805 | 1.43k | if (doProps) { |
806 | 1.43k | int tmp = Handle_Props(client, p_subscribe_ack->props, |
807 | 1.43k | (packet_obj != NULL), 1); |
808 | 1.43k | p_subscribe_ack->props = NULL; |
809 | 1.43k | if (tmp != MQTT_CODE_SUCCESS) { |
810 | 0 | rc = tmp; |
811 | 0 | } |
812 | 1.43k | } |
813 | 1.43k | #endif |
814 | 1.43k | } |
815 | 1.86k | break; |
816 | 3.77k | } |
817 | 1.30k | case MQTT_PACKET_TYPE_UNSUBSCRIBE_ACK: |
818 | 1.30k | { |
819 | 1.30k | MqttUnsubscribeAck unsubscribe_ack, |
820 | 1.30k | *p_unsubscribe_ack = &unsubscribe_ack; |
821 | 1.30k | if (packet_obj) { |
822 | 687 | p_unsubscribe_ack = (MqttUnsubscribeAck*)packet_obj; |
823 | 687 | } |
824 | 620 | else { |
825 | 620 | XMEMSET(p_unsubscribe_ack, 0, sizeof(MqttUnsubscribeAck)); |
826 | 620 | } |
827 | 1.30k | #ifdef WOLFMQTT_V5 |
828 | 1.30k | p_unsubscribe_ack->protocol_level = client->protocol_level; |
829 | 1.30k | #endif |
830 | 1.30k | rc = MqttDecode_UnsubscribeAck(rx_buf, rx_len, p_unsubscribe_ack); |
831 | 1.30k | if (rc >= 0) { |
832 | 1.14k | packet_id = p_unsubscribe_ack->packet_id; |
833 | 1.14k | #ifdef WOLFMQTT_V5 |
834 | 1.14k | if (doProps) { |
835 | 1.14k | int tmp = Handle_Props(client, p_unsubscribe_ack->props, |
836 | 1.14k | (packet_obj != NULL), 1); |
837 | 1.14k | p_unsubscribe_ack->props = NULL; |
838 | 1.14k | if (tmp != MQTT_CODE_SUCCESS) { |
839 | 0 | rc = tmp; |
840 | 0 | } |
841 | 1.14k | } |
842 | 1.14k | #endif |
843 | 1.14k | } |
844 | 1.30k | break; |
845 | 3.77k | } |
846 | 1.35k | case MQTT_PACKET_TYPE_PING_RESP: |
847 | 1.35k | { |
848 | 1.35k | MqttPing ping, *p_ping = &ping; |
849 | 1.35k | if (packet_obj) { |
850 | 717 | p_ping = (MqttPing*)packet_obj; |
851 | 717 | } |
852 | 640 | else { |
853 | 640 | XMEMSET(p_ping, 0, sizeof(MqttPing)); |
854 | 640 | } |
855 | 1.35k | rc = MqttDecode_Ping(rx_buf, rx_len, p_ping); |
856 | 1.35k | break; |
857 | 3.77k | } |
858 | 6.01k | case MQTT_PACKET_TYPE_AUTH: |
859 | 6.01k | { |
860 | 6.01k | #ifdef WOLFMQTT_V5 |
861 | 6.01k | MqttAuth auth, *p_auth = &auth; |
862 | 6.01k | if (packet_obj) { |
863 | 3.27k | p_auth = (MqttAuth*)packet_obj; |
864 | 3.27k | } |
865 | 2.73k | else { |
866 | 2.73k | XMEMSET(p_auth, 0, sizeof(MqttAuth)); |
867 | 2.73k | } |
868 | 6.01k | rc = MqttDecode_Auth(rx_buf, rx_len, p_auth); |
869 | 6.01k | if (rc >= 0 && doProps) { |
870 | 4.46k | int tmp = Handle_Props(client, p_auth->props, |
871 | 4.46k | (packet_obj != NULL), 1); |
872 | 4.46k | p_auth->props = NULL; |
873 | 4.46k | if (tmp != MQTT_CODE_SUCCESS) { |
874 | 0 | rc = tmp; |
875 | 0 | } |
876 | 4.46k | } |
877 | | #else |
878 | | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PACKET_TYPE); |
879 | | #endif /* WOLFMQTT_V5 */ |
880 | 6.01k | break; |
881 | 3.77k | } |
882 | 4.17k | case MQTT_PACKET_TYPE_DISCONNECT: |
883 | 4.17k | { |
884 | 4.17k | #ifdef WOLFMQTT_V5 |
885 | 4.17k | MqttDisconnect disc, *p_disc = &disc; |
886 | 4.17k | if (packet_obj) { |
887 | 2.24k | p_disc = (MqttDisconnect*)packet_obj; |
888 | 2.24k | } |
889 | 1.93k | else { |
890 | 1.93k | XMEMSET(p_disc, 0, sizeof(MqttDisconnect)); |
891 | 1.93k | } |
892 | 4.17k | rc = MqttDecode_Disconnect(rx_buf, rx_len, p_disc); |
893 | 4.17k | if (rc >= 0 && doProps) { |
894 | 3.12k | int tmp = Handle_Props(client, p_disc->props, |
895 | 3.12k | (packet_obj != NULL), 1); |
896 | 3.12k | p_disc->props = NULL; |
897 | 3.12k | if (tmp != MQTT_CODE_SUCCESS) { |
898 | 0 | rc = tmp; |
899 | 0 | } |
900 | 3.12k | } |
901 | 4.17k | #ifdef WOLFMQTT_DISCONNECT_CB |
902 | | /* Call disconnect callback with reason code */ |
903 | 4.17k | if ((packet_obj != NULL) && client->disconnect_cb) { |
904 | 0 | client->disconnect_cb(client, p_disc->reason_code, |
905 | 0 | client->disconnect_ctx); |
906 | 0 | } |
907 | 4.17k | #endif |
908 | | #else |
909 | | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PACKET_TYPE); |
910 | | #endif /* WOLFMQTT_V5 */ |
911 | 4.17k | break; |
912 | 3.77k | } |
913 | 8 | case MQTT_PACKET_TYPE_CONNECT: |
914 | 8 | case MQTT_PACKET_TYPE_SUBSCRIBE: |
915 | 8 | case MQTT_PACKET_TYPE_UNSUBSCRIBE: |
916 | 8 | case MQTT_PACKET_TYPE_PING_REQ: |
917 | 8 | case MQTT_PACKET_TYPE_ANY: |
918 | 144 | case MQTT_PACKET_TYPE_RESERVED: |
919 | 144 | default: |
920 | | /* these type are only encoded by client */ |
921 | 144 | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PACKET_TYPE); |
922 | 144 | break; |
923 | 25.0k | } /* switch (packet_type) */ |
924 | 25.0k | } |
925 | | |
926 | 25.0k | if (ppacket_id) { |
927 | 25.0k | *ppacket_id = packet_id; |
928 | 25.0k | } |
929 | | |
930 | 25.0k | (void)client; |
931 | 25.0k | (void)doProps; |
932 | | |
933 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
934 | | PRINTF("MqttClient_DecodePacket: Rc %d, Len %d, Type %s (%d), ID %d," |
935 | | " QoS %d, doProps %d", |
936 | | rc, rx_len, MqttPacket_TypeDesc(packet_type), packet_type, packet_id, |
937 | | packet_qos, doProps); |
938 | | #endif |
939 | | |
940 | 25.0k | return rc; |
941 | 25.0k | } |
942 | | |
943 | | static int MqttClient_HandlePacket(MqttClient* client, |
944 | | MqttPacketType packet_type, void *packet_obj, MqttPublishResp* resp, |
945 | | int timeout_ms) |
946 | 13.6k | { |
947 | 13.6k | int rc = MQTT_CODE_SUCCESS; |
948 | 13.6k | MqttQoS packet_qos = MQTT_QOS_0; |
949 | 13.6k | word16 packet_id = 0; |
950 | | |
951 | 13.6k | if (client == NULL || packet_obj == NULL) { |
952 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
953 | 0 | } |
954 | | |
955 | | /* make sure the response defaults to no ACK */ |
956 | 13.6k | resp->packet_type = MQTT_PACKET_TYPE_RESERVED; |
957 | | |
958 | 13.6k | switch (packet_type) |
959 | 13.6k | { |
960 | 493 | case MQTT_PACKET_TYPE_CONNECT_ACK: |
961 | 493 | { |
962 | 493 | rc = MqttClient_DecodePacket(client, client->rx_buf, |
963 | 493 | client->packet.buf_len, packet_obj, &packet_type, &packet_qos, |
964 | 493 | &packet_id, 1); |
965 | 493 | break; |
966 | 0 | } |
967 | 4.99k | case MQTT_PACKET_TYPE_PUBLISH: |
968 | 4.99k | { |
969 | 4.99k | MqttPublish* publish = (MqttPublish*)packet_obj; |
970 | 4.99k | if (publish->stat.read != MQTT_MSG_PAYLOAD2) { |
971 | 4.99k | rc = MqttClient_DecodePacket(client, client->rx_buf, |
972 | 4.99k | client->packet.buf_len, packet_obj, &packet_type, |
973 | 4.99k | &packet_qos, &packet_id, 1); |
974 | 4.99k | if (rc <= 0) { |
975 | 3.59k | return rc; |
976 | 3.59k | } |
977 | 4.99k | } |
978 | 0 | else { |
979 | | /* packet ID and QoS were already established */ |
980 | 0 | packet_id = publish->packet_id; |
981 | 0 | packet_qos = publish->qos; |
982 | 0 | } |
983 | | |
984 | 1.39k | rc = MqttClient_Publish_ReadPayload(client, publish, timeout_ms); |
985 | | |
986 | | /* MQTT_CODE_CONTINUE means the payload is not fully read yet. Return |
987 | | * to the caller and keep publish->props and the read state intact so |
988 | | * the non-blocking re-entry can resume. */ |
989 | 1.39k | if (rc == MQTT_CODE_CONTINUE) { |
990 | 0 | break; |
991 | 0 | } |
992 | | |
993 | | /* The publish read is terminal here, whether it succeeded or failed |
994 | | * to deliver (e.g. no msg_cb, or the callback returned an error). |
995 | | * Reset the read state and free the retained V5 property list for |
996 | | * every terminal result: the properties are intentionally kept |
997 | | * through decode/callback and were previously freed only on the |
998 | | * success path, so an error return would leak the property pool |
999 | | * (or heap, under WOLFMQTT_DYN_PROP). Also resetting the read state |
1000 | | * keeps a caller that logs the error and retries from re-entering on |
1001 | | * stale MQTT_MSG_PAYLOAD2 state. */ |
1002 | 1.39k | publish->stat.read = MQTT_MSG_BEGIN; /* reset state */ |
1003 | 1.39k | #ifdef WOLFMQTT_V5 |
1004 | | /* Free the properties */ |
1005 | 1.39k | MqttProps_Free(publish->props); |
1006 | 1.39k | publish->props = NULL; |
1007 | 1.39k | #endif |
1008 | | |
1009 | 1.39k | if (rc < 0) { |
1010 | 197 | break; |
1011 | 197 | } |
1012 | | |
1013 | | /* Handle QoS */ |
1014 | 1.20k | if (packet_qos == MQTT_QOS_0) { |
1015 | | /* we are done, no QoS response */ |
1016 | 490 | break; |
1017 | 490 | } |
1018 | | |
1019 | 710 | #ifdef WOLFMQTT_V5 |
1020 | | /* Copy response code in case changed by callback */ |
1021 | 710 | resp->reason_code = publish->resp.reason_code; |
1022 | 710 | #endif |
1023 | | /* Populate information needed for ack */ |
1024 | 710 | resp->packet_type = (packet_qos == MQTT_QOS_1) ? |
1025 | 479 | MQTT_PACKET_TYPE_PUBLISH_ACK : |
1026 | 710 | MQTT_PACKET_TYPE_PUBLISH_REC; |
1027 | 710 | resp->packet_id = packet_id; |
1028 | 710 | break; |
1029 | 1.20k | } |
1030 | 443 | case MQTT_PACKET_TYPE_PUBLISH_ACK: |
1031 | 1.10k | case MQTT_PACKET_TYPE_PUBLISH_REC: |
1032 | 1.80k | case MQTT_PACKET_TYPE_PUBLISH_REL: |
1033 | 2.57k | case MQTT_PACKET_TYPE_PUBLISH_COMP: |
1034 | 2.57k | { |
1035 | | #if defined(WOLFMQTT_V5) && defined(WOLFMQTT_DEBUG_CLIENT) |
1036 | | MqttPublishResp* publish_resp = (MqttPublishResp*)packet_obj; |
1037 | | #endif |
1038 | 2.57k | rc = MqttClient_DecodePacket(client, client->rx_buf, |
1039 | 2.57k | client->packet.buf_len, packet_obj, &packet_type, |
1040 | 2.57k | &packet_qos, &packet_id, 1); |
1041 | 2.57k | if (rc <= 0) { |
1042 | 0 | return rc; |
1043 | 0 | } |
1044 | | |
1045 | | #if defined(WOLFMQTT_V5) && defined(WOLFMQTT_DEBUG_CLIENT) |
1046 | | PRINTF("\tPublish response: reason code %d, Type %s (%d)," |
1047 | | " ID %d, QoS %d", |
1048 | | publish_resp->reason_code, |
1049 | | MqttPacket_TypeDesc(packet_type), |
1050 | | packet_type, packet_id, packet_qos); |
1051 | | #endif |
1052 | | |
1053 | | /* Only ACK publish Received or Release QoS levels */ |
1054 | 2.57k | if (packet_type != MQTT_PACKET_TYPE_PUBLISH_REC && |
1055 | 1.90k | packet_type != MQTT_PACKET_TYPE_PUBLISH_REL) { |
1056 | 1.21k | break; |
1057 | 1.21k | } |
1058 | | |
1059 | 1.36k | #ifdef WOLFMQTT_V5 |
1060 | | /* A v5 broker rejects a QoS 2 PUBLISH at the PUBREC stage with a |
1061 | | * reason code >= 0x80 (e.g. not authorized, quota exceeded, topic |
1062 | | * name invalid, payload format invalid). Per [MQTT-4.3.3] the |
1063 | | * exchange is then complete and the sender MUST NOT send a PUBREL. |
1064 | | * Surface the rejection instead of advancing the handshake, which |
1065 | | * would emit an illegal PUBREL and then block waiting for a PUBCOMP |
1066 | | * the broker will never send. The QoS 1 PUBACK and the QoS 2 |
1067 | | * PUBCOMP reason codes are checked by the caller after the wait. |
1068 | | * Note (WOLFMQTT_MULTITHREAD): when a separate thread drives reads |
1069 | | * and processes this PUBREC, it receives this error directly and |
1070 | | * the publishing thread's PUBCOMP pending response is not marked |
1071 | | * done, so that publish blocks until cmd_timeout_ms. This matches |
1072 | | * the pre-existing behavior (which left the publisher waiting on a |
1073 | | * PUBCOMP after an illegal PUBREL) and is not made worse here. */ |
1074 | 1.36k | if (packet_type == MQTT_PACKET_TYPE_PUBLISH_REC && |
1075 | 666 | client->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5 && |
1076 | 0 | (((MqttPublishResp*)packet_obj)->reason_code & 0x80)) { |
1077 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PUBLISH_REJECTED); |
1078 | 0 | } |
1079 | 1.36k | #endif |
1080 | | |
1081 | | /* Populate information needed for ack */ |
1082 | 1.36k | resp->packet_type = packet_type+1; /* next ack */ |
1083 | 1.36k | resp->packet_id = packet_id; |
1084 | 1.36k | break; |
1085 | 1.36k | } |
1086 | 706 | case MQTT_PACKET_TYPE_SUBSCRIBE_ACK: |
1087 | 706 | { |
1088 | 706 | rc = MqttClient_DecodePacket(client, client->rx_buf, |
1089 | 706 | client->packet.buf_len, packet_obj, &packet_type, &packet_qos, |
1090 | 706 | &packet_id, 1); |
1091 | 706 | break; |
1092 | 1.36k | } |
1093 | 562 | case MQTT_PACKET_TYPE_UNSUBSCRIBE_ACK: |
1094 | 562 | { |
1095 | 562 | rc = MqttClient_DecodePacket(client, client->rx_buf, |
1096 | 562 | client->packet.buf_len, packet_obj, &packet_type, &packet_qos, |
1097 | 562 | &packet_id, 1); |
1098 | 562 | break; |
1099 | 1.36k | } |
1100 | 599 | case MQTT_PACKET_TYPE_PING_RESP: |
1101 | 599 | { |
1102 | 599 | rc = MqttClient_DecodePacket(client, client->rx_buf, |
1103 | 599 | client->packet.buf_len, packet_obj, &packet_type, &packet_qos, |
1104 | 599 | &packet_id, 1); |
1105 | 599 | break; |
1106 | 1.36k | } |
1107 | 2.22k | case MQTT_PACKET_TYPE_AUTH: |
1108 | 2.22k | { |
1109 | 2.22k | #ifdef WOLFMQTT_V5 |
1110 | 2.22k | rc = MqttClient_DecodePacket(client, client->rx_buf, |
1111 | 2.22k | client->packet.buf_len, packet_obj, &packet_type, &packet_qos, |
1112 | 2.22k | &packet_id, 1); |
1113 | | /* The decoded AUTH properties (including AUTH_DATA) were pointers |
1114 | | * into rx_buf and have now been delivered to the callback and |
1115 | | * freed. Scrub rx_buf so the enhanced-authentication material does |
1116 | | * not linger until the next read, matching MqttClient_Auth and the |
1117 | | * v5 CONNACK path. This runs under lockRecv (held by MqttReadStart |
1118 | | * in MqttClient_WaitType), so no additional locking is needed. */ |
1119 | 2.22k | CLIENT_FORCE_ZERO(client->rx_buf, client->rx_buf_len); |
1120 | | #else |
1121 | | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PACKET_TYPE); |
1122 | | #endif |
1123 | 2.22k | break; |
1124 | 1.36k | } |
1125 | | |
1126 | 1.51k | case MQTT_PACKET_TYPE_DISCONNECT: |
1127 | 1.51k | { |
1128 | 1.51k | #ifdef WOLFMQTT_V5 |
1129 | 1.51k | rc = MqttClient_DecodePacket(client, client->rx_buf, |
1130 | 1.51k | client->packet.buf_len, packet_obj, &packet_type, &packet_qos, |
1131 | 1.51k | &packet_id, 1); |
1132 | | #else |
1133 | | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PACKET_TYPE); |
1134 | | #endif |
1135 | 1.51k | break; |
1136 | 1.36k | } |
1137 | 0 | case MQTT_PACKET_TYPE_CONNECT: |
1138 | 0 | case MQTT_PACKET_TYPE_SUBSCRIBE: |
1139 | 0 | case MQTT_PACKET_TYPE_UNSUBSCRIBE: |
1140 | 0 | case MQTT_PACKET_TYPE_PING_REQ: |
1141 | 0 | case MQTT_PACKET_TYPE_ANY: |
1142 | 0 | case MQTT_PACKET_TYPE_RESERVED: |
1143 | 0 | default: |
1144 | | /* these types are only sent from client and should not be sent |
1145 | | * by broker */ |
1146 | 0 | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PACKET_TYPE); |
1147 | 0 | break; |
1148 | 13.6k | } /* switch (packet_type) */ |
1149 | | |
1150 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1151 | | if (rc < 0) { |
1152 | | PRINTF("MqttClient_HandlePacket: Rc %d, Type %s (%d), QoS %d, ID %d", |
1153 | | rc, MqttPacket_TypeDesc(packet_type), packet_type, packet_qos, |
1154 | | packet_id); |
1155 | | } |
1156 | | #endif |
1157 | | |
1158 | 10.0k | return rc; |
1159 | 13.6k | } |
1160 | | |
1161 | | static inline int MqttIsPubRespPacket(int packet_type) |
1162 | 19.8k | { |
1163 | 19.8k | return (packet_type == MQTT_PACKET_TYPE_PUBLISH_ACK /* Acknowledgment */ || |
1164 | 18.7k | packet_type == MQTT_PACKET_TYPE_PUBLISH_REC /* Received */ || |
1165 | 17.8k | packet_type == MQTT_PACKET_TYPE_PUBLISH_REL /* Release */ || |
1166 | 16.5k | packet_type == MQTT_PACKET_TYPE_PUBLISH_COMP /* Complete */); |
1167 | 19.8k | } |
1168 | | |
1169 | | #ifdef WOLFMQTT_MULTITHREAD |
1170 | | /* this function will return: |
1171 | | * MQTT_CODE_CONTINUE indicating found, but not marked done |
1172 | | * MQTT_CODE_ERROR_NOT_FOUND: Not found |
1173 | | * Any other response is from the the packet_ret |
1174 | | */ |
1175 | | WOLFMQTT_LOCAL int MqttClient_CheckPendResp(MqttClient *client, byte wait_type, |
1176 | | word16 wait_packet_id) |
1177 | | { |
1178 | | int rc; |
1179 | | MqttPendResp *pendResp = NULL; |
1180 | | |
1181 | | /* Check to see if packet type and id have already completed */ |
1182 | | rc = wm_SemLock(&client->lockClient); |
1183 | | if (rc == 0) { |
1184 | | if (MqttClient_RespList_Find(client, (MqttPacketType)wait_type, |
1185 | | wait_packet_id, &pendResp)) |
1186 | | { |
1187 | | if ((pendResp != NULL) && (pendResp->packetDone)) { |
1188 | | /* pending response is already done, so return */ |
1189 | | rc = pendResp->packet_ret; |
1190 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1191 | | PRINTF("PendResp Check Done %p: Rc %d", pendResp, rc); |
1192 | | #endif |
1193 | | MqttClient_RespList_Remove(client, pendResp); |
1194 | | } |
1195 | | else { |
1196 | | /* item not done */ |
1197 | | rc = MQTT_CODE_CONTINUE; |
1198 | | } |
1199 | | } |
1200 | | else { |
1201 | | /* item not found */ |
1202 | | rc = MQTT_CODE_ERROR_NOT_FOUND; |
1203 | | } |
1204 | | wm_SemUnlock(&client->lockClient); |
1205 | | } |
1206 | | return rc; |
1207 | | } |
1208 | | #endif /* WOLFMQTT_MULTITHREAD */ |
1209 | | |
1210 | | /* Helper for clearing the contents of an object buffer based on packet type */ |
1211 | | static void MqttClient_PacketReset(MqttPacketType packet_type, void* packet_obj) |
1212 | 9.67k | { |
1213 | 9.67k | size_t objSz = 0; |
1214 | 9.67k | size_t offset = sizeof(MqttMsgStat); |
1215 | | /* The MqttPendResp offset is added only for the types whose struct embeds |
1216 | | * a pendResp member (right after MqttMsgStat). Ack-only types (CONNECT_ACK, |
1217 | | * SUBSCRIBE_ACK, UNSUBSCRIBE_ACK, DISCONNECT) have no pendResp, so adding |
1218 | | * it there would skip live fields - matching the per-type handling in |
1219 | | * MqttSNClient_PacketReset. */ |
1220 | 9.67k | switch (packet_type) { |
1221 | 0 | case MQTT_PACKET_TYPE_CONNECT: |
1222 | 0 | objSz = sizeof(MqttConnect); |
1223 | | #ifdef WOLFMQTT_MULTITHREAD |
1224 | | offset += sizeof(MqttPendResp); |
1225 | | #endif |
1226 | 0 | break; |
1227 | 493 | case MQTT_PACKET_TYPE_CONNECT_ACK: |
1228 | 493 | objSz = sizeof(MqttConnectAck); |
1229 | 493 | break; |
1230 | 1.01k | case MQTT_PACKET_TYPE_PUBLISH: |
1231 | 1.01k | objSz = sizeof(MqttPublish); |
1232 | | #ifdef WOLFMQTT_MULTITHREAD |
1233 | | offset += sizeof(MqttPendResp); |
1234 | | #endif |
1235 | 1.01k | break; |
1236 | 443 | case MQTT_PACKET_TYPE_PUBLISH_ACK: |
1237 | 1.10k | case MQTT_PACKET_TYPE_PUBLISH_REC: |
1238 | 1.80k | case MQTT_PACKET_TYPE_PUBLISH_REL: |
1239 | 2.57k | case MQTT_PACKET_TYPE_PUBLISH_COMP: |
1240 | 2.57k | objSz = sizeof(MqttPublishResp); |
1241 | | #ifdef WOLFMQTT_MULTITHREAD |
1242 | | offset += sizeof(MqttPendResp); |
1243 | | #endif |
1244 | 2.57k | break; |
1245 | 0 | case MQTT_PACKET_TYPE_SUBSCRIBE: |
1246 | 0 | objSz = sizeof(MqttSubscribe); |
1247 | | #ifdef WOLFMQTT_MULTITHREAD |
1248 | | offset += sizeof(MqttPendResp); |
1249 | | #endif |
1250 | 0 | break; |
1251 | 706 | case MQTT_PACKET_TYPE_SUBSCRIBE_ACK: |
1252 | 706 | objSz = sizeof(MqttSubscribeAck); |
1253 | 706 | break; |
1254 | 0 | case MQTT_PACKET_TYPE_UNSUBSCRIBE: |
1255 | 0 | objSz = sizeof(MqttUnsubscribe); |
1256 | | #ifdef WOLFMQTT_MULTITHREAD |
1257 | | offset += sizeof(MqttPendResp); |
1258 | | #endif |
1259 | 0 | break; |
1260 | 562 | case MQTT_PACKET_TYPE_UNSUBSCRIBE_ACK: |
1261 | 562 | objSz = sizeof(MqttUnsubscribeAck); |
1262 | 562 | break; |
1263 | 0 | case MQTT_PACKET_TYPE_PING_REQ: |
1264 | 599 | case MQTT_PACKET_TYPE_PING_RESP: |
1265 | 599 | objSz = sizeof(MqttPing); |
1266 | | #ifdef WOLFMQTT_MULTITHREAD |
1267 | | offset += sizeof(MqttPendResp); |
1268 | | #endif |
1269 | 599 | break; |
1270 | 2.22k | case MQTT_PACKET_TYPE_AUTH: |
1271 | 2.22k | #ifdef WOLFMQTT_V5 |
1272 | 2.22k | objSz = sizeof(MqttAuth); |
1273 | | #ifdef WOLFMQTT_MULTITHREAD |
1274 | | offset += sizeof(MqttPendResp); |
1275 | | #endif |
1276 | 2.22k | #endif |
1277 | 2.22k | break; |
1278 | 1.51k | case MQTT_PACKET_TYPE_DISCONNECT: |
1279 | 1.51k | #ifdef WOLFMQTT_V5 |
1280 | 1.51k | objSz = sizeof(MqttDisconnect); |
1281 | 1.51k | #endif |
1282 | 1.51k | break; |
1283 | 0 | case MQTT_PACKET_TYPE_ANY: |
1284 | 0 | case MQTT_PACKET_TYPE_RESERVED: |
1285 | 0 | default: |
1286 | 0 | break; |
1287 | 9.67k | } /* switch (packet_type) */ |
1288 | 9.67k | if (objSz > offset) { |
1289 | 9.07k | XMEMSET((byte*)packet_obj + offset, 0, objSz - offset); |
1290 | 9.07k | } |
1291 | 9.67k | } |
1292 | | |
1293 | | static int MqttClient_WaitType(MqttClient *client, void *packet_obj, |
1294 | | byte wait_type, word16 wait_packet_id, int timeout_ms) |
1295 | 9.62k | { |
1296 | 9.62k | int rc = MQTT_CODE_SUCCESS; |
1297 | 9.62k | word16 packet_id; |
1298 | 9.62k | MqttPacketType packet_type; |
1299 | 9.62k | MqttQoS packet_qos = MQTT_QOS_0; |
1300 | | #ifdef WOLFMQTT_MULTITHREAD |
1301 | | MqttPendResp *pendResp; |
1302 | | #endif |
1303 | 9.62k | MqttMsgStat* mms_stat; |
1304 | 9.62k | int waitMatchFound; |
1305 | 9.62k | void* use_packet_obj = NULL; |
1306 | | |
1307 | 9.62k | if (client == NULL || packet_obj == NULL) { |
1308 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
1309 | 0 | } |
1310 | | |
1311 | | /* all packet type structures must have MqttMsgStat at top */ |
1312 | 9.62k | mms_stat = (MqttMsgStat*)packet_obj; |
1313 | | |
1314 | 18.2k | wait_again: |
1315 | | |
1316 | | /* initialize variables */ |
1317 | 18.2k | packet_id = 0; |
1318 | 18.2k | packet_type = MQTT_PACKET_TYPE_RESERVED; |
1319 | | #ifdef WOLFMQTT_MULTITHREAD |
1320 | | pendResp = NULL; |
1321 | | #endif |
1322 | 18.2k | waitMatchFound = 0; |
1323 | | |
1324 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1325 | | #ifdef WOLFMQTT_NONBLOCK |
1326 | | if (client->lastRc != MQTT_CODE_CONTINUE) |
1327 | | #endif |
1328 | | { |
1329 | | PRINTF("MqttClient_WaitType: Type %s (%d), ID %d, State %d-%d", |
1330 | | MqttPacket_TypeDesc((MqttPacketType)wait_type), |
1331 | | wait_type, wait_packet_id, mms_stat->read, mms_stat->write); |
1332 | | } |
1333 | | #endif |
1334 | | |
1335 | 18.2k | switch (mms_stat->read) |
1336 | 18.2k | { |
1337 | 13.4k | case MQTT_MSG_BEGIN: |
1338 | 13.4k | { |
1339 | | #ifdef WOLFMQTT_MULTITHREAD |
1340 | | /* Check to see if packet type and id have already completed */ |
1341 | | rc = MqttClient_CheckPendResp(client, wait_type, wait_packet_id); |
1342 | | if (rc != MQTT_CODE_ERROR_NOT_FOUND && rc != MQTT_CODE_CONTINUE) { |
1343 | | return rc; |
1344 | | } |
1345 | | #endif |
1346 | | |
1347 | 13.4k | if ((rc = MqttReadStart(client, mms_stat)) != 0) { |
1348 | 0 | return rc; |
1349 | 0 | } |
1350 | | |
1351 | 13.4k | mms_stat->read = MQTT_MSG_WAIT; |
1352 | 13.4k | } |
1353 | 13.4k | FALL_THROUGH; |
1354 | | |
1355 | 13.4k | case MQTT_MSG_WAIT: |
1356 | 14.0k | case MQTT_MSG_HEADER: |
1357 | 14.0k | { |
1358 | | /* Wait for packet */ |
1359 | 14.0k | rc = MqttPacket_Read(client, client->rx_buf, client->rx_buf_len, |
1360 | 14.0k | timeout_ms); |
1361 | | /* handle failure */ |
1362 | 14.0k | if (rc <= 0) { |
1363 | | #ifdef WOLFMQTT_NONBLOCK |
1364 | | if (rc == MQTT_CODE_CONTINUE && |
1365 | | (client->packet.stat > MQTT_PK_BEGIN || |
1366 | | client->read.total > 0) |
1367 | | ) { |
1368 | | /* advance state, since we received some data */ |
1369 | | mms_stat->read = MQTT_MSG_HEADER; |
1370 | | } |
1371 | | #endif |
1372 | 2.72k | break; |
1373 | 2.72k | } |
1374 | | |
1375 | | /* advance state, since we received some data */ |
1376 | 11.3k | mms_stat->read = MQTT_MSG_HEADER; |
1377 | | |
1378 | | /* capture length read */ |
1379 | 11.3k | client->packet.buf_len = rc; |
1380 | | |
1381 | | /* Decode Packet - get type, qos and id */ |
1382 | 11.3k | rc = MqttClient_DecodePacket(client, client->rx_buf, |
1383 | 11.3k | client->packet.buf_len, NULL, &packet_type, &packet_qos, |
1384 | 11.3k | &packet_id, 1); |
1385 | 11.3k | if (rc < 0) { |
1386 | 1.68k | break; |
1387 | 1.68k | } |
1388 | | |
1389 | 9.67k | MqttClient_PacketReset(packet_type, &client->msg); |
1390 | | |
1391 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1392 | | PRINTF("Read Packet: Len %d, Type %d, ID %d", |
1393 | | client->packet.buf_len, packet_type, packet_id); |
1394 | | #endif |
1395 | | |
1396 | | /* Ping response is special case, no payload */ |
1397 | 9.67k | if (packet_type != MQTT_PACKET_TYPE_PING_RESP) { |
1398 | 9.07k | mms_stat->read = MQTT_MSG_PAYLOAD; |
1399 | 9.07k | } |
1400 | 599 | else { |
1401 | 599 | mms_stat->read = MQTT_MSG_WAIT; |
1402 | 599 | } |
1403 | 9.67k | } |
1404 | 9.67k | FALL_THROUGH; |
1405 | | |
1406 | 13.6k | case MQTT_MSG_PAYLOAD: |
1407 | 13.6k | case MQTT_MSG_PAYLOAD2: |
1408 | 13.6k | { |
1409 | 13.6k | MqttPublishResp resp; |
1410 | 13.6k | MqttPacketType use_packet_type; |
1411 | | |
1412 | | /* Determine if we received data for this request */ |
1413 | 13.6k | if ((wait_type == MQTT_PACKET_TYPE_ANY || |
1414 | 8.38k | wait_type == packet_type || |
1415 | 7.82k | (MqttIsPubRespPacket(packet_type) && |
1416 | 2.20k | MqttIsPubRespPacket(wait_type))) && |
1417 | 6.21k | (wait_packet_id == 0 || wait_packet_id == packet_id)) |
1418 | 5.80k | { |
1419 | 5.80k | use_packet_obj = packet_obj; |
1420 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1421 | | PRINTF("Using INCOMING packet_obj %p", use_packet_obj); |
1422 | | #endif |
1423 | 5.80k | if (packet_type == wait_type || |
1424 | 5.60k | wait_type == MQTT_PACKET_TYPE_ANY) { |
1425 | | /* Only stop waiting when matched or waiting for "any" */ |
1426 | 5.60k | waitMatchFound = 1; |
1427 | 5.60k | } |
1428 | 5.80k | } |
1429 | 7.84k | else { |
1430 | | #ifdef WOLFMQTT_MULTITHREAD |
1431 | | rc = wm_SemLock(&client->lockClient); |
1432 | | if (rc != 0) { |
1433 | | break; /* error */ |
1434 | | } |
1435 | | #endif |
1436 | | |
1437 | | /* use generic packet object */ |
1438 | 7.84k | use_packet_obj = &client->msg; |
1439 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1440 | | PRINTF("Using SHARED packet_obj %p", use_packet_obj); |
1441 | | #endif |
1442 | | |
1443 | | #ifdef WOLFMQTT_MULTITHREAD |
1444 | | wm_SemUnlock(&client->lockClient); |
1445 | | #endif |
1446 | 7.84k | } |
1447 | 13.6k | use_packet_type = packet_type; |
1448 | | |
1449 | | #ifdef WOLFMQTT_MULTITHREAD |
1450 | | /* Check to see if we have a pending response for this packet */ |
1451 | | pendResp = NULL; |
1452 | | rc = wm_SemLock(&client->lockClient); |
1453 | | if (rc == 0) { |
1454 | | if (MqttClient_RespList_Find(client, packet_type, packet_id, |
1455 | | &pendResp)) { |
1456 | | /* we found packet match this incoming read packet */ |
1457 | | pendResp->packetProcessing = 1; |
1458 | | if (pendResp->packet_obj != packet_obj) { |
1459 | | use_packet_obj = pendResp->packet_obj; |
1460 | | use_packet_type = pendResp->packet_type; |
1461 | | /* req from another thread... not a match */ |
1462 | | waitMatchFound = 0; |
1463 | | } |
1464 | | } |
1465 | | wm_SemUnlock(&client->lockClient); |
1466 | | } |
1467 | | else { |
1468 | | break; /* error */ |
1469 | | } |
1470 | | #endif /* WOLFMQTT_MULTITHREAD */ |
1471 | | |
1472 | | /* for payload state packet type is always publish */ |
1473 | 13.6k | if (use_packet_type == MQTT_PACKET_TYPE_RESERVED && |
1474 | 3.97k | (mms_stat->read == MQTT_MSG_PAYLOAD || |
1475 | 0 | mms_stat->read == MQTT_MSG_PAYLOAD2)) |
1476 | 3.97k | { |
1477 | 3.97k | use_packet_type = MQTT_PACKET_TYPE_PUBLISH; |
1478 | 3.97k | } |
1479 | | /* cache publish packet id and qos for MqttClient_HandlePacket payload */ |
1480 | 13.6k | if (use_packet_type == MQTT_PACKET_TYPE_PUBLISH && |
1481 | 4.99k | mms_stat->read == MQTT_MSG_PAYLOAD && use_packet_obj != NULL) |
1482 | 4.99k | { |
1483 | 4.99k | MqttObject* obj = (MqttObject*)use_packet_obj; |
1484 | 4.99k | obj->publish.qos = packet_qos; |
1485 | 4.99k | obj->publish.packet_id = packet_id; |
1486 | 4.99k | } |
1487 | | |
1488 | | /* Perform packet handling for publish callback and QoS */ |
1489 | 13.6k | XMEMSET(&resp, 0, sizeof(resp)); |
1490 | 13.6k | rc = MqttClient_HandlePacket(client, use_packet_type, |
1491 | 13.6k | use_packet_obj, &resp, timeout_ms); |
1492 | | |
1493 | | /* if using the shared packet object, make sure the original |
1494 | | * state is correct for publish payload 2 (continued) */ |
1495 | 13.6k | if (use_packet_obj != NULL && use_packet_obj != mms_stat && |
1496 | 7.84k | ((MqttMsgStat*)use_packet_obj)->read == MQTT_MSG_PAYLOAD2) { |
1497 | 0 | mms_stat->read = MQTT_MSG_PAYLOAD2; |
1498 | 0 | } |
1499 | | |
1500 | | #ifdef WOLFMQTT_NONBLOCK |
1501 | | if (rc == MQTT_CODE_CONTINUE) { |
1502 | | break; |
1503 | | } |
1504 | | #endif |
1505 | | |
1506 | | /* handle success case */ |
1507 | 13.6k | if (rc >= 0) { |
1508 | 9.86k | rc = MQTT_CODE_SUCCESS; |
1509 | 9.86k | } |
1510 | 3.79k | else { |
1511 | | /* error, break */ |
1512 | 3.79k | break; |
1513 | 3.79k | } |
1514 | | |
1515 | | #ifdef WOLFMQTT_MULTITHREAD |
1516 | | if (pendResp) { |
1517 | | /* Mark pending response entry done */ |
1518 | | if (wm_SemLock(&client->lockClient) == 0) { |
1519 | | pendResp->packetDone = 1; |
1520 | | pendResp->packet_ret = rc; |
1521 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1522 | | PRINTF("PendResp Done %p", pendResp); |
1523 | | #endif |
1524 | | pendResp = NULL; |
1525 | | wm_SemUnlock(&client->lockClient); |
1526 | | } |
1527 | | } |
1528 | | #endif /* WOLFMQTT_MULTITHREAD */ |
1529 | | |
1530 | | /* Determine if we are sending ACK or done */ |
1531 | 9.86k | if (MqttIsPubRespPacket(resp.packet_type)) { |
1532 | | /* if we get here, then we are sending an ACK */ |
1533 | 2.07k | mms_stat->read = MQTT_MSG_ACK; |
1534 | 2.07k | mms_stat->ack = MQTT_MSG_WAIT; |
1535 | | |
1536 | | /* setup ACK in shared context */ |
1537 | 2.07k | XMEMCPY(&client->packetAck, &resp, sizeof(MqttPublishResp)); |
1538 | 2.07k | #ifdef WOLFMQTT_V5 |
1539 | 2.07k | client->packetAck.protocol_level = client->protocol_level; |
1540 | 2.07k | #endif |
1541 | 2.07k | } |
1542 | | |
1543 | | /* done reading */ |
1544 | 9.86k | MqttReadStop(client, mms_stat); |
1545 | 9.86k | break; |
1546 | 13.6k | } |
1547 | | |
1548 | 164 | case MQTT_MSG_ACK: |
1549 | | /* go to write section below */ |
1550 | 164 | break; |
1551 | | |
1552 | 0 | case MQTT_MSG_AUTH: |
1553 | 0 | default: |
1554 | 0 | { |
1555 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1556 | | PRINTF("MqttClient_WaitType: Invalid read state %d!", |
1557 | | mms_stat->read); |
1558 | | #endif |
1559 | 0 | rc = MQTT_CODE_ERROR_STAT; |
1560 | 0 | break; |
1561 | 0 | } |
1562 | 18.2k | } /* switch (mms_stat->read) */ |
1563 | | |
1564 | 18.2k | switch (mms_stat->ack) |
1565 | 18.2k | { |
1566 | 16.0k | case MQTT_MSG_BEGIN: |
1567 | | /* wait for read to set ack */ |
1568 | 16.0k | break; |
1569 | | |
1570 | 2.07k | case MQTT_MSG_WAIT: |
1571 | 2.07k | { |
1572 | | /* Flag write active / lock mutex */ |
1573 | 2.07k | if ((rc = MqttWriteStart(client, mms_stat)) != 0) { |
1574 | 0 | break; |
1575 | 0 | } |
1576 | 2.07k | mms_stat->ack = MQTT_MSG_ACK; |
1577 | 2.07k | } |
1578 | 2.07k | FALL_THROUGH; |
1579 | | |
1580 | 2.14k | case MQTT_MSG_ACK: |
1581 | 2.14k | { |
1582 | | /* send ack */ |
1583 | 2.14k | rc = MqttEncode_PublishResp(client->tx_buf, client->tx_buf_len, |
1584 | 2.14k | client->packetAck.packet_type, &client->packetAck); |
1585 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1586 | | PRINTF("MqttEncode_PublishResp: Len %d, Type %s (%d), ID %d", |
1587 | | rc, MqttPacket_TypeDesc(client->packetAck.packet_type), |
1588 | | client->packetAck.packet_type, client->packetAck.packet_id); |
1589 | | #endif |
1590 | 2.14k | if (rc < 0) { |
1591 | 125 | MqttWriteStop(client, mms_stat); |
1592 | 125 | break; |
1593 | 125 | } |
1594 | | |
1595 | 2.02k | client->write.len = rc; |
1596 | | /* Note: static analyzer complains about set, but not used here. |
1597 | | * Keeping it to ensure no future issues with rc > 0 */ |
1598 | 2.02k | rc = MQTT_CODE_SUCCESS; |
1599 | 2.02k | (void)rc; /* inhibit clang-analyzer-deadcode.DeadStores */ |
1600 | | |
1601 | 2.02k | mms_stat->ack = MQTT_MSG_HEADER; |
1602 | 2.02k | } |
1603 | 2.02k | FALL_THROUGH; |
1604 | | |
1605 | 2.02k | case MQTT_MSG_HEADER: |
1606 | 2.02k | { |
1607 | 2.02k | int xfer = client->write.len; |
1608 | | |
1609 | | /* Send publish response packet */ |
1610 | 2.02k | rc = MqttPacket_Write(client, client->tx_buf, xfer); |
1611 | | #ifdef WOLFMQTT_NONBLOCK |
1612 | | if (rc == MQTT_CODE_CONTINUE) { |
1613 | | /* keep send mutex locked and return to caller */ |
1614 | | /* must keep send locked */ |
1615 | | return rc; |
1616 | | } |
1617 | | #endif |
1618 | 2.02k | MqttWriteStop(client, mms_stat); |
1619 | 2.02k | if (rc == xfer) { |
1620 | 1.48k | rc = MQTT_CODE_SUCCESS; /* success */ |
1621 | 1.48k | } |
1622 | | |
1623 | 2.02k | mms_stat->ack = MQTT_MSG_BEGIN; /* reset write state */ |
1624 | 2.02k | break; |
1625 | 2.02k | } |
1626 | | |
1627 | 0 | case MQTT_MSG_AUTH: |
1628 | 0 | case MQTT_MSG_PAYLOAD: |
1629 | 0 | case MQTT_MSG_PAYLOAD2: |
1630 | 0 | default: |
1631 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1632 | | PRINTF("MqttClient_WaitType: Invalid ack state %d!", |
1633 | | mms_stat->ack); |
1634 | | #endif |
1635 | 0 | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_STAT); |
1636 | 0 | break; |
1637 | 18.2k | } /* switch (mms_stat->ack) */ |
1638 | | |
1639 | | /* no data read or ack done, then reset state */ |
1640 | 18.2k | if (mms_stat->read == MQTT_MSG_WAIT) { |
1641 | 2.97k | mms_stat->read = MQTT_MSG_BEGIN; |
1642 | 2.97k | } |
1643 | | |
1644 | | #ifdef WOLFMQTT_NONBLOCK |
1645 | | /* if nonblocking and some data has been read, do not release read lock */ |
1646 | | if (rc == MQTT_CODE_CONTINUE && mms_stat->read > MQTT_MSG_WAIT) { |
1647 | | return rc; |
1648 | | } |
1649 | | #endif |
1650 | | |
1651 | 18.2k | MqttReadStop(client, mms_stat); |
1652 | | |
1653 | | #ifdef WOLFMQTT_NONBLOCK |
1654 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1655 | | #ifdef WOLFMQTT_MULTITHREAD |
1656 | | if (wm_SemLock(&client->lockClient) == 0) |
1657 | | #endif |
1658 | | { |
1659 | | client->lastRc = rc; |
1660 | | #ifdef WOLFMQTT_MULTITHREAD |
1661 | | wm_SemUnlock(&client->lockClient); |
1662 | | #endif |
1663 | | } |
1664 | | #endif /* WOLFMQTT_DEBUG_CLIENT */ |
1665 | | if (rc == MQTT_CODE_CONTINUE) { |
1666 | | return rc; |
1667 | | } |
1668 | | #endif |
1669 | | |
1670 | 18.2k | if (rc < 0) { |
1671 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1672 | | if (rc != MQTT_CODE_CONTINUE) { |
1673 | | PRINTF("MqttClient_WaitType: Failure: %s (%d)", |
1674 | | MqttClient_ReturnCodeToString(rc), rc); |
1675 | | } |
1676 | | #endif |
1677 | 7.93k | return rc; |
1678 | 7.93k | } |
1679 | | |
1680 | 10.2k | if (!waitMatchFound) { |
1681 | | /* if we get here, then the we are still waiting for a packet */ |
1682 | 8.59k | mms_stat->read = MQTT_MSG_BEGIN; |
1683 | | #ifdef WOLFMQTT_NONBLOCK |
1684 | | /* for non-blocking return with code continue instead of waiting again |
1685 | | * if called with packet type and id of 'any' */ |
1686 | | if (wait_type == MQTT_PACKET_TYPE_ANY && wait_packet_id == 0) { |
1687 | | return MQTT_CODE_CONTINUE; |
1688 | | } |
1689 | | #endif |
1690 | 8.59k | MQTT_TRACE_MSG("Wait Again"); |
1691 | 8.59k | goto wait_again; |
1692 | 8.59k | } |
1693 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1694 | | if (rc != MQTT_CODE_CONTINUE) { |
1695 | | PRINTF("MqttClient_WaitType: rc %d, state %d-%d-%d", |
1696 | | rc, mms_stat->read, mms_stat->write, mms_stat->ack); |
1697 | | } |
1698 | | #endif |
1699 | | |
1700 | | |
1701 | 1.68k | return rc; |
1702 | 10.2k | } |
1703 | | |
1704 | | |
1705 | | /* Public Functions */ |
1706 | | int MqttClient_Init(MqttClient *client, MqttNet* net, |
1707 | | MqttMsgCb msg_cb, |
1708 | | byte* tx_buf, int tx_buf_len, |
1709 | | byte* rx_buf, int rx_buf_len, |
1710 | | int cmd_timeout_ms) |
1711 | 0 | { |
1712 | 0 | int rc = MQTT_CODE_SUCCESS; |
1713 | | |
1714 | | /* Check arguments */ |
1715 | 0 | if (client == NULL || |
1716 | 0 | tx_buf == NULL || tx_buf_len <= 0 || |
1717 | 0 | rx_buf == NULL || rx_buf_len <= 0 || |
1718 | 0 | cmd_timeout_ms < 0) { |
1719 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
1720 | 0 | } |
1721 | | |
1722 | | /* Initialize the client structure to zero */ |
1723 | 0 | XMEMSET(client, 0, sizeof(MqttClient)); |
1724 | | |
1725 | | /* Setup client structure */ |
1726 | 0 | client->msg_cb = msg_cb; |
1727 | 0 | client->tx_buf = tx_buf; |
1728 | 0 | client->tx_buf_len = tx_buf_len; |
1729 | 0 | client->rx_buf = rx_buf; |
1730 | 0 | client->rx_buf_len = rx_buf_len; |
1731 | 0 | client->cmd_timeout_ms = cmd_timeout_ms; |
1732 | 0 | #ifdef WOLFMQTT_V5 |
1733 | | /* Initialize to this build's Maximum QoS. Handle_Props will narrow |
1734 | | * this if the server advertises a lower MQTT_PROP_MAX_QOS. */ |
1735 | 0 | client->max_qos = (MqttQoS)WOLFMQTT_MAX_QOS; |
1736 | 0 | client->retain_avail = 1; |
1737 | 0 | client->protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL; |
1738 | 0 | rc = MqttProps_Init(); |
1739 | 0 | #endif |
1740 | |
|
1741 | | #ifdef WOLFMQTT_MULTITHREAD |
1742 | | if (rc == 0) { |
1743 | | rc = wm_SemInit(&client->lockSend); |
1744 | | } |
1745 | | if (rc == 0) { |
1746 | | rc = wm_SemInit(&client->lockRecv); |
1747 | | } |
1748 | | if (rc == 0) { |
1749 | | rc = wm_SemInit(&client->lockClient); |
1750 | | } |
1751 | | #ifdef ENABLE_MQTT_CURL |
1752 | | if (rc == 0) { |
1753 | | rc = wm_SemInit(&client->lockCURL); |
1754 | | } |
1755 | | #endif |
1756 | | #endif |
1757 | |
|
1758 | 0 | if (rc == 0) { |
1759 | | /* Init socket */ |
1760 | 0 | rc = MqttSocket_Init(client, net); |
1761 | 0 | } |
1762 | |
|
1763 | 0 | if (rc != 0) { |
1764 | | /* Cleanup if init failed */ |
1765 | 0 | MqttClient_DeInit(client); |
1766 | 0 | } |
1767 | |
|
1768 | 0 | return rc; |
1769 | 0 | } |
1770 | | |
1771 | | void MqttClient_DeInit(MqttClient *client) |
1772 | 0 | { |
1773 | 0 | if (client != NULL) { |
1774 | | #ifdef WOLFMQTT_MULTITHREAD |
1775 | | (void)wm_SemFree(&client->lockSend); |
1776 | | (void)wm_SemFree(&client->lockRecv); |
1777 | | (void)wm_SemFree(&client->lockClient); |
1778 | | #ifdef ENABLE_MQTT_CURL |
1779 | | (void)wm_SemFree(&client->lockCURL); |
1780 | | #endif |
1781 | | #endif |
1782 | 0 | #ifdef WOLFMQTT_V5 |
1783 | 0 | (void)MqttProps_ShutDown(); |
1784 | 0 | #endif |
1785 | 0 | } |
1786 | 0 | } |
1787 | | |
1788 | | #ifdef WOLFMQTT_DISCONNECT_CB |
1789 | | int MqttClient_SetDisconnectCallback(MqttClient *client, |
1790 | | MqttDisconnectCb discCb, void* ctx) |
1791 | 0 | { |
1792 | 0 | if (client == NULL) |
1793 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
1794 | | |
1795 | 0 | client->disconnect_cb = discCb; |
1796 | 0 | client->disconnect_ctx = ctx; |
1797 | |
|
1798 | 0 | return MQTT_CODE_SUCCESS; |
1799 | 0 | } |
1800 | | #endif |
1801 | | |
1802 | | #ifdef WOLFMQTT_PROPERTY_CB |
1803 | | int MqttClient_SetPropertyCallback(MqttClient *client, MqttPropertyCb propCb, |
1804 | | void* ctx) |
1805 | 0 | { |
1806 | 0 | if (client == NULL) |
1807 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
1808 | | |
1809 | 0 | client->property_cb = propCb; |
1810 | 0 | client->property_ctx = ctx; |
1811 | |
|
1812 | 0 | return MQTT_CODE_SUCCESS; |
1813 | 0 | } |
1814 | | #endif |
1815 | | |
1816 | | int MqttClient_Connect(MqttClient *client, MqttConnect *mc_connect) |
1817 | 4.61k | { |
1818 | 4.61k | int rc; |
1819 | | |
1820 | | /* Validate required arguments */ |
1821 | 4.61k | if (client == NULL || mc_connect == NULL) { |
1822 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
1823 | 0 | } |
1824 | | |
1825 | 4.61k | if (mc_connect->stat.write == MQTT_MSG_BEGIN) { |
1826 | | /* Warn if credentials are being sent without TLS */ |
1827 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1828 | | if ((mc_connect->username != NULL || mc_connect->password != NULL) && |
1829 | | !(MqttClient_Flags(client, 0, 0) & MQTT_CLIENT_FLAG_IS_TLS)) { |
1830 | | PRINTF("Warning: MQTT credentials are being sent without TLS"); |
1831 | | } |
1832 | | #endif |
1833 | | |
1834 | 4.61k | #ifndef WOLFMQTT_NO_TIME |
1835 | | /* Disarm auto keep-alive for the handshake; it is armed only after |
1836 | | * CONNACK is accepted at the end of this function. Fully cancel any |
1837 | | * ping left mid-exchange by a prior connection so its held locks and |
1838 | | * pending response are released before the new write starts, rather |
1839 | | * than leaving the ping state machine to stall a later wait. Must run |
1840 | | * before MqttWriteStart so the send lock is free when it is taken. |
1841 | | * Propagate a cancel failure - only reachable if wm_SemLock itself |
1842 | | * errors (e.g. on ThreadX), since it otherwise blocks until acquired - |
1843 | | * rather than starting the write with locks the abandoned ping may |
1844 | | * still hold. */ |
1845 | 4.61k | client->keep_alive_sec = 0; |
1846 | 4.61k | client->keep_alive_from_server = 0; |
1847 | 4.61k | rc = MqttClient_CancelMessage(client, |
1848 | 4.61k | (MqttObject*)&client->keep_alive_ping); |
1849 | 4.61k | if (rc != MQTT_CODE_SUCCESS) { |
1850 | 0 | return rc; |
1851 | 0 | } |
1852 | 4.61k | #endif |
1853 | | |
1854 | | /* Flag write active / lock mutex */ |
1855 | 4.61k | if ((rc = MqttWriteStart(client, &mc_connect->stat)) != 0) { |
1856 | 0 | return rc; |
1857 | 0 | } |
1858 | | |
1859 | 4.61k | #ifdef WOLFMQTT_V5 |
1860 | | /* Use specified protocol version if set */ |
1861 | 4.61k | mc_connect->protocol_level = client->protocol_level; |
1862 | | |
1863 | | /* Reset server-supplied session limits so stale values from a |
1864 | | * prior broker do not leak across reconnects. An accepted CONNACK |
1865 | | * will repopulate these in Handle_ConnectAck_Props. Initialize to |
1866 | | * this build's Maximum QoS so the runtime guard in MqttPublishMsg |
1867 | | * caps publishes even before CONNACK is processed. */ |
1868 | 4.61k | client->max_qos = (MqttQoS)WOLFMQTT_MAX_QOS; |
1869 | 4.61k | client->retain_avail = 1; |
1870 | 4.61k | client->packet_sz_max = 0; |
1871 | 4.61k | #endif |
1872 | | |
1873 | | /* Encode the connect packet */ |
1874 | 4.61k | rc = MqttEncode_Connect(client->tx_buf, client->tx_buf_len, mc_connect); |
1875 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
1876 | | PRINTF("MqttClient_EncodePacket: Len %d, Type %s (%d), ID %d, QoS %d", |
1877 | | rc, MqttPacket_TypeDesc(MQTT_PACKET_TYPE_CONNECT), |
1878 | | MQTT_PACKET_TYPE_CONNECT, 0, 0); |
1879 | | #endif |
1880 | 4.61k | if (rc <= 0) { |
1881 | | /* Encode failed: tx_buf may hold partial plaintext credentials. |
1882 | | * Zero the full buffer before MqttWriteStop releases lockSend |
1883 | | * so no other thread can see residual data. */ |
1884 | 203 | CLIENT_FORCE_ZERO(client->tx_buf, client->tx_buf_len); |
1885 | 203 | MqttWriteStop(client, &mc_connect->stat); |
1886 | 203 | return rc; |
1887 | 203 | } |
1888 | 4.40k | client->write.len = rc; |
1889 | | |
1890 | | #ifdef WOLFMQTT_MULTITHREAD |
1891 | | rc = wm_SemLock(&client->lockClient); |
1892 | | if (rc == 0) { |
1893 | | /* inform other threads of expected response */ |
1894 | | rc = MqttClient_RespList_Add(client, MQTT_PACKET_TYPE_CONNECT_ACK, |
1895 | | 0, &mc_connect->pendResp, &mc_connect->ack); |
1896 | | wm_SemUnlock(&client->lockClient); |
1897 | | } |
1898 | | if (rc != 0) { |
1899 | | /* Save write.len before MqttWriteStop zeroes client->write */ |
1900 | | int xfer = client->write.len; |
1901 | | /* Clear tx_buf to remove plaintext credentials BEFORE |
1902 | | * MqttWriteStop releases lockSend, so another thread cannot |
1903 | | * race in and repopulate tx_buf before it is scrubbed. */ |
1904 | | CLIENT_FORCE_ZERO(client->tx_buf, xfer); |
1905 | | MqttWriteStop(client, &mc_connect->stat); |
1906 | | return rc; /* Error locking client */ |
1907 | | } |
1908 | | #endif |
1909 | | |
1910 | 4.40k | mc_connect->stat.write = MQTT_MSG_HEADER; |
1911 | 4.40k | } |
1912 | 4.40k | if (mc_connect->stat.write == MQTT_MSG_HEADER) { |
1913 | 4.40k | int xfer = client->write.len; |
1914 | | |
1915 | | /* Send connect packet */ |
1916 | 4.40k | rc = MqttPacket_Write(client, client->tx_buf, xfer); |
1917 | | #ifdef WOLFMQTT_NONBLOCK |
1918 | | if (rc == MQTT_CODE_CONTINUE |
1919 | | #ifdef WOLFMQTT_ALLOW_NODATA_UNLOCK |
1920 | | && client->write.total > 0 |
1921 | | #endif |
1922 | | ) { |
1923 | | /* keep send locked and return early. |
1924 | | * Note: tx_buf still contains credentials until write completes */ |
1925 | | return rc; |
1926 | | } |
1927 | | #endif |
1928 | | /* Clear tx_buf to remove any plaintext credentials from memory |
1929 | | * BEFORE MqttWriteStop releases lockSend, so another thread cannot |
1930 | | * race in and populate tx_buf before it is scrubbed. |
1931 | | * Use xfer (saved before MqttWriteStop zeroes client->write). */ |
1932 | 4.40k | CLIENT_FORCE_ZERO(client->tx_buf, xfer); |
1933 | 4.40k | MqttWriteStop(client, &mc_connect->stat); |
1934 | | |
1935 | 4.40k | if (rc != xfer) { |
1936 | 3.01k | MqttClient_CancelMessage(client, (MqttObject*)mc_connect); |
1937 | 3.01k | return rc; |
1938 | 3.01k | } |
1939 | | |
1940 | 1.39k | #ifdef WOLFMQTT_V5 |
1941 | | /* Enhanced authentication */ |
1942 | 1.39k | if (client->enable_eauth == 1) { |
1943 | 0 | mc_connect->stat.write = MQTT_MSG_AUTH; |
1944 | 0 | } |
1945 | 1.39k | else |
1946 | 1.39k | #endif |
1947 | 1.39k | { |
1948 | 1.39k | mc_connect->stat.write = MQTT_MSG_WAIT; |
1949 | 1.39k | } |
1950 | 1.39k | } |
1951 | | |
1952 | 1.39k | #ifdef WOLFMQTT_V5 |
1953 | | /* Enhanced authentication */ |
1954 | 1.39k | if (mc_connect->protocol_level > MQTT_CONNECT_PROTOCOL_LEVEL_4 && |
1955 | 1.39k | mc_connect->stat.write == MQTT_MSG_AUTH) |
1956 | 0 | { |
1957 | 0 | MqttAuth auth, *p_auth = &auth; |
1958 | 0 | MqttProp* prop, *conn_prop; |
1959 | | |
1960 | | /* Find the AUTH property in the connect structure */ |
1961 | 0 | for (conn_prop = mc_connect->props; |
1962 | 0 | (conn_prop != NULL) && (conn_prop->type != MQTT_PROP_AUTH_METHOD); |
1963 | 0 | conn_prop = conn_prop->next) { |
1964 | 0 | } |
1965 | 0 | if (conn_prop == NULL) { |
1966 | | #ifdef WOLFMQTT_MULTITHREAD |
1967 | | if (wm_SemLock(&client->lockClient) == 0) { |
1968 | | MqttClient_RespList_Remove(client, &mc_connect->pendResp); |
1969 | | wm_SemUnlock(&client->lockClient); |
1970 | | } |
1971 | | #endif |
1972 | | /* AUTH property was not set in connect structure */ |
1973 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
1974 | 0 | } |
1975 | | |
1976 | 0 | XMEMSET((void*)p_auth, 0, sizeof(MqttAuth)); |
1977 | | |
1978 | | /* Set the authentication reason */ |
1979 | 0 | p_auth->reason_code = MQTT_REASON_CONT_AUTH; |
1980 | | |
1981 | | /* Use the same authentication method property from connect */ |
1982 | 0 | prop = MqttProps_Add(&p_auth->props); |
1983 | 0 | if (prop == NULL) { |
1984 | | #ifdef WOLFMQTT_MULTITHREAD |
1985 | | if (wm_SemLock(&client->lockClient) == 0) { |
1986 | | MqttClient_RespList_Remove(client, &mc_connect->pendResp); |
1987 | | wm_SemUnlock(&client->lockClient); |
1988 | | } |
1989 | | #endif |
1990 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MEMORY); |
1991 | 0 | } |
1992 | 0 | prop->type = MQTT_PROP_AUTH_METHOD; |
1993 | 0 | prop->data_str.str = conn_prop->data_str.str; |
1994 | 0 | prop->data_str.len = conn_prop->data_str.len; |
1995 | | |
1996 | | /* Send the AUTH packet */ |
1997 | 0 | rc = MqttClient_Auth(client, p_auth); |
1998 | 0 | MqttClient_PropsFree(p_auth->props); |
1999 | | #ifdef WOLFMQTT_NONBLOCK |
2000 | | if (rc == MQTT_CODE_CONTINUE) |
2001 | | return rc; |
2002 | | #endif |
2003 | 0 | if (rc < 0) { |
2004 | | #ifdef WOLFMQTT_MULTITHREAD |
2005 | | if (wm_SemLock(&client->lockClient) == 0) { |
2006 | | MqttClient_RespList_Remove(client, &mc_connect->pendResp); |
2007 | | wm_SemUnlock(&client->lockClient); |
2008 | | } |
2009 | | #endif |
2010 | 0 | return rc; |
2011 | 0 | } |
2012 | 0 | mc_connect->stat.write = MQTT_MSG_WAIT; |
2013 | 0 | } |
2014 | 1.39k | #endif /* WOLFMQTT_V5 */ |
2015 | | |
2016 | | /* Wait for connect ack packet */ |
2017 | 1.39k | rc = MqttClient_WaitType(client, &mc_connect->ack, |
2018 | 1.39k | MQTT_PACKET_TYPE_CONNECT_ACK, 0, client->cmd_timeout_ms); |
2019 | | #if defined(WOLFMQTT_NONBLOCK) || defined(WOLFMQTT_MULTITHREAD) |
2020 | | if (rc == MQTT_CODE_CONTINUE) |
2021 | | return rc; |
2022 | | #endif |
2023 | | |
2024 | | #ifdef WOLFMQTT_MULTITHREAD |
2025 | | if (wm_SemLock(&client->lockClient) == 0) { |
2026 | | MqttClient_RespList_Remove(client, &mc_connect->pendResp); |
2027 | | wm_SemUnlock(&client->lockClient); |
2028 | | } |
2029 | | #endif |
2030 | | |
2031 | 1.39k | #ifdef WOLFMQTT_V5 |
2032 | | /* Scrub the decoded v5 CONNACK from rx_buf. Its properties (e.g. the |
2033 | | * MQTT_PROP_AUTH_DATA SASL server-final blob used by enhanced |
2034 | | * authentication) decode as pointers into rx_buf and would otherwise |
2035 | | * linger until the next read overwrites them - for an idle or QoS-0-only |
2036 | | * client, potentially the process lifetime. MqttClient_WaitType already |
2037 | | * delivered and freed the property list above, so the bytes are consumed |
2038 | | * and no live pointer into rx_buf remains. Same hardening as |
2039 | | * MqttClient_Auth; v3.1.1 CONNACK carries no properties so gate on v5. */ |
2040 | 1.39k | if (mc_connect->protocol_level > MQTT_CONNECT_PROTOCOL_LEVEL_4) { |
2041 | | #ifdef WOLFMQTT_MULTITHREAD |
2042 | | /* Hold lockRecv so the scrub cannot race a concurrent rx_buf read. If |
2043 | | * the lock cannot be taken, still scrub: leaving the AUTH_DATA |
2044 | | * plaintext behind is worse than an unsynchronized wipe. */ |
2045 | | if (wm_SemLock(&client->lockRecv) == 0) { |
2046 | | CLIENT_FORCE_ZERO(client->rx_buf, client->rx_buf_len); |
2047 | | wm_SemUnlock(&client->lockRecv); |
2048 | | } |
2049 | | else { |
2050 | | CLIENT_FORCE_ZERO(client->rx_buf, client->rx_buf_len); |
2051 | | } |
2052 | | #else |
2053 | 1.39k | CLIENT_FORCE_ZERO(client->rx_buf, client->rx_buf_len); |
2054 | 1.39k | #endif |
2055 | 1.39k | } |
2056 | 1.39k | #endif |
2057 | | |
2058 | | /* reset state */ |
2059 | 1.39k | mc_connect->stat.write = MQTT_MSG_BEGIN; |
2060 | | |
2061 | | /* CONNACK was received and decoded, but the broker refused the |
2062 | | * connection. The specific reason is in mc_connect->ack.return_code |
2063 | | * (MqttConnectAckReturnCodes for v3.1.1, MqttReasonCodes for v5). */ |
2064 | 1.39k | if (rc == MQTT_CODE_SUCCESS && |
2065 | 59 | mc_connect->ack.return_code != MQTT_CONNECT_ACK_CODE_ACCEPTED) { |
2066 | 19 | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_CONNECT_REFUSED); |
2067 | 19 | } |
2068 | | |
2069 | 1.39k | #ifndef WOLFMQTT_NO_TIME |
2070 | 1.39k | if (rc == MQTT_CODE_SUCCESS) { |
2071 | | /* Connection accepted: arm auto keep-alive. A v5 Server Keep Alive |
2072 | | * (applied while processing CONNACK) takes precedence, including a |
2073 | | * value of 0 which disables keep-alive per [MQTT-3.1.2.11.2]; |
2074 | | * otherwise use the client-requested value. */ |
2075 | 40 | if (!client->keep_alive_from_server) { |
2076 | 40 | client->keep_alive_sec = mc_connect->keep_alive_sec; |
2077 | 40 | } |
2078 | | /* Baseline the idle timer from the completed handshake so the first |
2079 | | * ping is scheduled a full interval out, not immediately. */ |
2080 | 40 | client->last_tx_time = WOLFMQTT_GET_TIME_S(); |
2081 | 40 | } |
2082 | 1.35k | else { |
2083 | | /* Connect failed or was refused: leave the scheduler disarmed. */ |
2084 | 1.35k | client->keep_alive_sec = 0; |
2085 | 1.35k | } |
2086 | 1.39k | client->keep_alive_from_server = 0; |
2087 | 1.39k | #endif |
2088 | | |
2089 | 1.39k | return rc; |
2090 | 1.39k | } |
2091 | | |
2092 | | static int MqttClient_Publish_ReadPayload(MqttClient* client, |
2093 | | MqttPublish* publish, int timeout_ms) |
2094 | 1.39k | { |
2095 | 1.39k | int rc = MQTT_CODE_SUCCESS; |
2096 | 1.39k | byte msg_done; |
2097 | | |
2098 | | /* Handle packet callback and read remaining payload */ |
2099 | 3.75k | do { |
2100 | | /* Determine if message is done */ |
2101 | 3.75k | msg_done = ((publish->buffer_pos + publish->buffer_len) >= |
2102 | 3.75k | publish->total_len) ? 1 : 0; |
2103 | | |
2104 | 3.75k | if (publish->buffer_new) { |
2105 | | /* Issue callback for new message (first time only) */ |
2106 | 1.19k | if (client->msg_cb) { |
2107 | | /* if using the temp publish message buffer, |
2108 | | then populate message context with client context */ |
2109 | 1.19k | if (publish->ctx == NULL && &client->msg.publish == publish) { |
2110 | 1.19k | publish->ctx = client->ctx; |
2111 | 1.19k | } |
2112 | 1.19k | rc = client->msg_cb(client, publish, publish->buffer_new, |
2113 | 1.19k | msg_done); |
2114 | 1.19k | if (rc != MQTT_CODE_SUCCESS) { |
2115 | 0 | return rc; |
2116 | 1.19k | }; |
2117 | 1.19k | } |
2118 | | |
2119 | | /* Reset topic name since valid on new message only */ |
2120 | 1.19k | publish->topic_name = NULL; |
2121 | 1.19k | publish->topic_name_len = 0; |
2122 | | |
2123 | 1.19k | publish->buffer_new = 0; |
2124 | 1.19k | } |
2125 | | |
2126 | | /* Read payload */ |
2127 | 3.75k | if (!msg_done) { |
2128 | 2.56k | int msg_len; |
2129 | | |
2130 | | /* add last length to position and reset len */ |
2131 | 2.56k | publish->buffer_pos += publish->buffer_len; |
2132 | 2.56k | publish->buffer_len = 0; |
2133 | | |
2134 | | /* set state to reading payload */ |
2135 | 2.56k | publish->stat.read = MQTT_MSG_PAYLOAD2; |
2136 | | |
2137 | 2.56k | msg_len = (publish->total_len - publish->buffer_pos); |
2138 | 2.56k | if (msg_len > client->rx_buf_len) { |
2139 | 1.36k | msg_len = client->rx_buf_len; |
2140 | 1.36k | } |
2141 | | |
2142 | | /* make sure there is something to read */ |
2143 | 2.56k | if (msg_len > 0) { |
2144 | 2.56k | rc = MqttSocket_Read(client, client->rx_buf, msg_len, |
2145 | 2.56k | timeout_ms); |
2146 | 2.56k | if (rc < 0) { |
2147 | 197 | break; |
2148 | 197 | } |
2149 | | |
2150 | | /* Update message */ |
2151 | 2.36k | publish->buffer = client->rx_buf; |
2152 | 2.36k | publish->buffer_len = rc; |
2153 | 2.36k | rc = MQTT_CODE_SUCCESS; /* mark success */ |
2154 | | |
2155 | 2.36k | msg_done = ((publish->buffer_pos + publish->buffer_len) >= |
2156 | 2.36k | publish->total_len) ? 1 : 0; |
2157 | | |
2158 | | /* Issue callback for additional publish payload */ |
2159 | 2.36k | if (client->msg_cb) { |
2160 | 2.36k | rc = client->msg_cb(client, publish, publish->buffer_new, |
2161 | 2.36k | msg_done); |
2162 | 2.36k | if (rc != MQTT_CODE_SUCCESS) { |
2163 | 0 | return rc; |
2164 | 2.36k | }; |
2165 | 2.36k | } |
2166 | 2.36k | } |
2167 | 2.56k | } |
2168 | 3.75k | } while (!msg_done); |
2169 | | |
2170 | | /* No message callback registered to deliver this incoming PUBLISH. The |
2171 | | * payload was drained above to keep the stream in sync, but the application |
2172 | | * never saw it. Return a distinct error instead of success so the caller is |
2173 | | * notified and, for QoS 1/2, MqttClient_HandlePacket does not falsely ACK |
2174 | | * the message as delivered. */ |
2175 | 1.39k | if (rc == MQTT_CODE_SUCCESS && client->msg_cb == NULL) { |
2176 | 0 | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_CALLBACK); |
2177 | 0 | } |
2178 | | |
2179 | 1.39k | return rc; |
2180 | 1.39k | } |
2181 | | |
2182 | | static int MqttClient_Publish_WritePayload(MqttClient *client, |
2183 | | MqttPublish *publish, MqttPublishCb pubCb) |
2184 | 333 | { |
2185 | 333 | int rc = MQTT_CODE_SUCCESS; |
2186 | | |
2187 | 333 | if (client == NULL || publish == NULL) |
2188 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
2189 | | |
2190 | 333 | if (pubCb) { /* use publish callback to get data */ |
2191 | 0 | word32 tmp_len; |
2192 | |
|
2193 | 0 | do { |
2194 | | /* use the client->write.len to handle non-blocking re-entry when |
2195 | | * new publish callback data is needed */ |
2196 | 0 | if (client->write.len == 0) { |
2197 | | /* Use the callback to get payload */ |
2198 | 0 | if ((client->write.len = pubCb(publish)) < 0) { |
2199 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
2200 | | PRINTF("Publish callback error %d", client->write.len); |
2201 | | #endif |
2202 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_CALLBACK); |
2203 | 0 | } |
2204 | | |
2205 | | /* Record how many valid bytes this callback produced (a short |
2206 | | * return marks the last read). Persisting it means a |
2207 | | * non-blocking resume does not mistake the in-progress chunk |
2208 | | * length for the fill length and drop the tail. */ |
2209 | 0 | publish->intBuf_cb_len = |
2210 | 0 | ((word32)client->write.len < publish->buffer_len) ? |
2211 | 0 | (word32)client->write.len : publish->buffer_len; |
2212 | 0 | } |
2213 | | |
2214 | 0 | tmp_len = publish->intBuf_cb_len; |
2215 | | |
2216 | | /* Send payload */ |
2217 | 0 | do { |
2218 | | /* Recompute the bytes remaining each pass so the final partial |
2219 | | * chunk copies only valid data and never reads past the end of |
2220 | | * the caller's payload buffer. */ |
2221 | 0 | client->write.len = (int)(tmp_len - publish->intBuf_pos); |
2222 | 0 | if (client->write.len > client->tx_buf_len) { |
2223 | 0 | client->write.len = client->tx_buf_len; |
2224 | 0 | } |
2225 | 0 | publish->intBuf_len = client->write.len; |
2226 | 0 | XMEMCPY(client->tx_buf, &publish->buffer[publish->intBuf_pos], |
2227 | 0 | client->write.len); |
2228 | |
|
2229 | 0 | rc = MqttPacket_Write(client, client->tx_buf, |
2230 | 0 | client->write.len); |
2231 | 0 | if (rc < 0) { |
2232 | 0 | return rc; |
2233 | 0 | } |
2234 | | |
2235 | 0 | publish->intBuf_pos += publish->intBuf_len; |
2236 | 0 | publish->intBuf_len = 0; |
2237 | |
|
2238 | 0 | } while (publish->intBuf_pos < tmp_len); |
2239 | | |
2240 | 0 | publish->buffer_pos += publish->intBuf_pos; |
2241 | 0 | publish->intBuf_pos = 0; |
2242 | 0 | client->write.len = 0; /* reset current write len */ |
2243 | |
|
2244 | 0 | } while (publish->buffer_pos < publish->total_len); |
2245 | 0 | } |
2246 | 333 | else if (publish->buffer_pos < publish->total_len) { |
2247 | 206 | if (publish->buffer_pos > 0) { |
2248 | 199 | client->write.len = (publish->total_len - publish->buffer_pos); |
2249 | 199 | if (client->write.len > client->tx_buf_len) { |
2250 | 112 | client->write.len = client->tx_buf_len; |
2251 | 112 | } |
2252 | | |
2253 | 199 | XMEMCPY(client->tx_buf, &publish->buffer[publish->buffer_pos], |
2254 | 199 | client->write.len); |
2255 | | |
2256 | 199 | #ifndef WOLFMQTT_NONBLOCK |
2257 | 199 | publish->intBuf_pos += client->write.len; |
2258 | 199 | #endif |
2259 | 199 | } |
2260 | | |
2261 | | /* Send packet and payload */ |
2262 | | #ifdef WOLFMQTT_NONBLOCK |
2263 | | rc = MqttPacket_Write(client, client->tx_buf, client->write.len); |
2264 | | if (rc < 0) { |
2265 | | return rc; |
2266 | | } |
2267 | | |
2268 | | /* ONLY if send was successful, update buffer position. |
2269 | | * Otherwise, MqttPacket_Write() will resume where it left off. */ |
2270 | | publish->buffer_pos += client->write.len; |
2271 | | |
2272 | | /* Check if we are done sending publish message */ |
2273 | | if (publish->buffer_pos < publish->buffer_len) { |
2274 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
2275 | | PRINTF("Publish Write: not done (%d remain)", |
2276 | | publish->buffer_len - publish->buffer_pos); |
2277 | | #endif |
2278 | | return MQTT_CODE_PUB_CONTINUE; |
2279 | | } |
2280 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
2281 | | else { |
2282 | | PRINTF("Publish Write: done"); |
2283 | | } |
2284 | | #endif |
2285 | | #else |
2286 | 574 | do { |
2287 | 574 | rc = MqttPacket_Write(client, client->tx_buf, client->write.len); |
2288 | 574 | if (rc < 0) { |
2289 | 146 | return rc; |
2290 | 146 | } |
2291 | | |
2292 | 428 | publish->intBuf_pos += publish->intBuf_len; |
2293 | 428 | publish->intBuf_len = 0; |
2294 | | |
2295 | | /* Check if we are done sending publish message */ |
2296 | 428 | if (publish->intBuf_pos >= publish->buffer_len) { |
2297 | 60 | rc = MQTT_CODE_SUCCESS; |
2298 | 60 | break; |
2299 | 60 | } |
2300 | | |
2301 | | /* Build packet payload to send */ |
2302 | 368 | client->write.len = (publish->buffer_len - publish->intBuf_pos); |
2303 | 368 | if (client->write.len > client->tx_buf_len) { |
2304 | 303 | client->write.len = client->tx_buf_len; |
2305 | 303 | } |
2306 | 368 | publish->intBuf_len = client->write.len; |
2307 | 368 | XMEMCPY(client->tx_buf, &publish->buffer[publish->intBuf_pos], |
2308 | 368 | client->write.len); |
2309 | 368 | } while (publish->intBuf_pos < publish->buffer_len); |
2310 | 60 | #endif |
2311 | | |
2312 | 60 | if (rc >= 0) { |
2313 | | /* If transferring more chunks */ |
2314 | 60 | publish->buffer_pos += publish->intBuf_pos; |
2315 | 60 | if (publish->buffer_pos < publish->total_len) { |
2316 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
2317 | | PRINTF("Publish Write: chunk (%d remain)", |
2318 | | publish->total_len - publish->buffer_pos); |
2319 | | #endif |
2320 | | |
2321 | | /* Build next payload to send */ |
2322 | 0 | client->write.len = (publish->total_len - publish->buffer_pos); |
2323 | 0 | if (client->write.len > client->tx_buf_len) { |
2324 | 0 | client->write.len = client->tx_buf_len; |
2325 | 0 | } |
2326 | 0 | rc = MQTT_CODE_PUB_CONTINUE; |
2327 | 0 | } |
2328 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
2329 | | else { |
2330 | | PRINTF("Publish Write: chunked done"); |
2331 | | } |
2332 | | #endif |
2333 | 60 | } |
2334 | 60 | } |
2335 | 187 | return rc; |
2336 | 333 | } |
2337 | | |
2338 | | static int MqttPublishMsg(MqttClient *client, MqttPublish *publish, |
2339 | | MqttPublishCb pubCb, int writeOnly) |
2340 | 997 | { |
2341 | 997 | int rc = MQTT_CODE_SUCCESS; |
2342 | 997 | MqttPacketType resp_type; |
2343 | | |
2344 | | /* Validate required arguments */ |
2345 | 997 | if (client == NULL || publish == NULL) { |
2346 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
2347 | 0 | } |
2348 | | |
2349 | 997 | #ifdef WOLFMQTT_V5 |
2350 | | /* Use specified protocol version if set */ |
2351 | 997 | publish->protocol_level = client->protocol_level; |
2352 | | |
2353 | | /* Validate publish request against server properties */ |
2354 | 997 | if ((publish->qos > client->max_qos) || |
2355 | 997 | ((publish->retain != 0) && (client->retain_avail == 0))) |
2356 | 0 | { |
2357 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_SERVER_PROP); |
2358 | 0 | } |
2359 | 997 | #endif |
2360 | | |
2361 | 997 | switch (publish->stat.write) |
2362 | 997 | { |
2363 | 997 | case MQTT_MSG_BEGIN: |
2364 | 997 | { |
2365 | | /* Flag write active / lock mutex */ |
2366 | 997 | if ((rc = MqttWriteStart(client, &publish->stat)) != 0) { |
2367 | 0 | return rc; |
2368 | 0 | } |
2369 | | |
2370 | | /* Encode the publish packet */ |
2371 | 997 | rc = MqttEncode_Publish(client->tx_buf, client->tx_buf_len, |
2372 | 997 | publish, pubCb ? 1 : 0); |
2373 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
2374 | | PRINTF("MqttClient_EncodePacket: Len %d, Type %s (%d), ID %d," |
2375 | | " QoS %d", |
2376 | | rc, MqttPacket_TypeDesc(MQTT_PACKET_TYPE_PUBLISH), |
2377 | | MQTT_PACKET_TYPE_PUBLISH, publish->packet_id, |
2378 | | publish->qos); |
2379 | | #endif |
2380 | 997 | if (rc <= 0) { |
2381 | 339 | MqttWriteStop(client, &publish->stat); |
2382 | 339 | return rc; |
2383 | 339 | } |
2384 | 658 | client->write.len = rc; |
2385 | | |
2386 | | #ifdef WOLFMQTT_MULTITHREAD |
2387 | | if (publish->qos > MQTT_QOS_0) { |
2388 | | resp_type = (publish->qos == MQTT_QOS_1) ? |
2389 | | MQTT_PACKET_TYPE_PUBLISH_ACK : |
2390 | | MQTT_PACKET_TYPE_PUBLISH_COMP; |
2391 | | |
2392 | | rc = wm_SemLock(&client->lockClient); |
2393 | | if (rc == 0) { |
2394 | | /* inform other threads of expected response */ |
2395 | | rc = MqttClient_RespList_Add(client, resp_type, |
2396 | | publish->packet_id, &publish->pendResp, &publish->resp); |
2397 | | wm_SemUnlock(&client->lockClient); |
2398 | | } |
2399 | | if (rc != 0) { |
2400 | | MqttWriteStop(client, &publish->stat); |
2401 | | return rc; /* Error locking client */ |
2402 | | } |
2403 | | } |
2404 | | #endif |
2405 | | |
2406 | 658 | publish->stat.write = MQTT_MSG_HEADER; |
2407 | 658 | } |
2408 | 658 | FALL_THROUGH; |
2409 | | |
2410 | 658 | case MQTT_MSG_HEADER: |
2411 | 658 | { |
2412 | 658 | int xfer = client->write.len; |
2413 | | |
2414 | | /* Send publish packet */ |
2415 | 658 | rc = MqttPacket_Write(client, client->tx_buf, xfer); |
2416 | | #ifdef WOLFMQTT_NONBLOCK |
2417 | | if (rc == MQTT_CODE_CONTINUE |
2418 | | #ifdef WOLFMQTT_ALLOW_NODATA_UNLOCK |
2419 | | && client->write.total > 0 |
2420 | | #endif |
2421 | | ) { |
2422 | | /* keep send locked and return early */ |
2423 | | return rc; |
2424 | | } |
2425 | | #endif |
2426 | 658 | client->write.len = 0; /* reset len, so publish chunk resets */ |
2427 | | |
2428 | | /* if failure or no data was written yet */ |
2429 | 658 | if (rc != xfer) { |
2430 | 325 | MqttWriteStop(client, &publish->stat); |
2431 | 325 | MqttClient_CancelMessage(client, (MqttObject*)publish); |
2432 | 325 | return rc; |
2433 | 325 | } |
2434 | | |
2435 | | /* advance state */ |
2436 | 333 | publish->stat.write = MQTT_MSG_PAYLOAD; |
2437 | 333 | } |
2438 | 333 | FALL_THROUGH; |
2439 | | |
2440 | 333 | case MQTT_MSG_PAYLOAD: |
2441 | 333 | { |
2442 | 333 | rc = MqttClient_Publish_WritePayload(client, publish, pubCb); |
2443 | | #ifdef WOLFMQTT_NONBLOCK |
2444 | | if (rc == MQTT_CODE_CONTINUE || rc == MQTT_CODE_PUB_CONTINUE) |
2445 | | return rc; |
2446 | | #endif |
2447 | 333 | MqttWriteStop(client, &publish->stat); |
2448 | 333 | if (rc < 0) { |
2449 | 146 | MqttClient_CancelMessage(client, (MqttObject*)publish); |
2450 | 146 | break; |
2451 | 146 | } |
2452 | | |
2453 | | /* if not expecting a reply then we are done */ |
2454 | 187 | if (publish->qos == MQTT_QOS_0) { |
2455 | 70 | break; |
2456 | 70 | } |
2457 | 117 | publish->stat.write = MQTT_MSG_WAIT; |
2458 | 117 | } |
2459 | 117 | FALL_THROUGH; |
2460 | | |
2461 | 117 | case MQTT_MSG_WAIT: |
2462 | 117 | { |
2463 | | /* Handle QoS */ |
2464 | 117 | if (publish->qos > MQTT_QOS_0) { |
2465 | | /* Determine packet type to wait for */ |
2466 | 117 | resp_type = (publish->qos == MQTT_QOS_1) ? |
2467 | 91 | MQTT_PACKET_TYPE_PUBLISH_ACK : |
2468 | 117 | MQTT_PACKET_TYPE_PUBLISH_COMP; |
2469 | | |
2470 | | #ifdef WOLFMQTT_MULTITHREAD |
2471 | | if (writeOnly) { |
2472 | | /* another thread will handle response */ |
2473 | | /* check if response already received from other thread */ |
2474 | | rc = MqttClient_CheckPendResp(client, resp_type, |
2475 | | publish->packet_id); |
2476 | | #ifndef WOLFMQTT_NONBLOCK |
2477 | | if (rc == MQTT_CODE_CONTINUE) { |
2478 | | /* mark success, let other thread handle response */ |
2479 | | rc = MQTT_CODE_SUCCESS; |
2480 | | } |
2481 | | #endif |
2482 | | } |
2483 | | else |
2484 | | #endif |
2485 | 117 | { |
2486 | 117 | (void)writeOnly; /* not used */ |
2487 | | |
2488 | | /* Wait for publish response packet */ |
2489 | 117 | rc = MqttClient_WaitType(client, &publish->resp, resp_type, |
2490 | 117 | publish->packet_id, client->cmd_timeout_ms); |
2491 | | |
2492 | 117 | #ifdef WOLFMQTT_V5 |
2493 | | /* A v5 broker can acknowledge a QoS>0 PUBLISH at the |
2494 | | * protocol layer yet still reject the message via a |
2495 | | * PUBACK/PUBCOMP reason code >= 0x80 (e.g. not authorized, |
2496 | | * quota exceeded, topic name invalid, payload format |
2497 | | * invalid). Surface that as an error so the caller does not |
2498 | | * treat a rejected message as delivered. Mirrors the |
2499 | | * CONNECT/SUBSCRIBE/UNSUBSCRIBE rejection handling. The |
2500 | | * protocol_level guard avoids misreading a stale byte for |
2501 | | * v3.1.1 ACKs, which carry no reason code (same guard the |
2502 | | * PUBREC check in MqttClient_HandlePacket uses). */ |
2503 | 117 | if (rc == MQTT_CODE_SUCCESS && |
2504 | 34 | client->protocol_level >= |
2505 | 34 | MQTT_CONNECT_PROTOCOL_LEVEL_5 && |
2506 | 0 | (publish->resp.reason_code & 0x80)) { |
2507 | 0 | rc = MQTT_TRACE_ERROR( |
2508 | 0 | MQTT_CODE_ERROR_PUBLISH_REJECTED); |
2509 | 0 | } |
2510 | 117 | #endif |
2511 | 117 | } |
2512 | | |
2513 | | #if defined(WOLFMQTT_NONBLOCK) || defined(WOLFMQTT_MULTITHREAD) |
2514 | | if (rc == MQTT_CODE_CONTINUE) |
2515 | | break; |
2516 | | #endif |
2517 | | #ifdef WOLFMQTT_MULTITHREAD |
2518 | | if (wm_SemLock(&client->lockClient) == 0) { |
2519 | | MqttClient_RespList_Remove(client, &publish->pendResp); |
2520 | | wm_SemUnlock(&client->lockClient); |
2521 | | } |
2522 | | #endif |
2523 | 117 | } |
2524 | 117 | break; |
2525 | 117 | } |
2526 | | |
2527 | 0 | case MQTT_MSG_ACK: |
2528 | 0 | case MQTT_MSG_AUTH: |
2529 | 0 | case MQTT_MSG_PAYLOAD2: |
2530 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
2531 | | PRINTF("MqttClient_Publish: Invalid state %d!", |
2532 | | publish->stat.write); |
2533 | | #endif |
2534 | 0 | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_STAT); |
2535 | 0 | break; |
2536 | 997 | } /* switch (publish->stat) */ |
2537 | | |
2538 | | /* reset state */ |
2539 | 333 | if ((rc != MQTT_CODE_PUB_CONTINUE) |
2540 | | #ifdef WOLFMQTT_NONBLOCK |
2541 | | && (rc != MQTT_CODE_CONTINUE) |
2542 | | #endif |
2543 | 333 | ) |
2544 | 333 | { |
2545 | 333 | publish->stat.write = MQTT_MSG_BEGIN; |
2546 | 333 | } |
2547 | 333 | if (rc > 0) { |
2548 | 0 | rc = MQTT_CODE_SUCCESS; |
2549 | 0 | } |
2550 | | |
2551 | 333 | return rc; |
2552 | 997 | } |
2553 | | |
2554 | | int MqttClient_Publish(MqttClient *client, MqttPublish *publish) |
2555 | 997 | { |
2556 | 997 | return MqttPublishMsg(client, publish, NULL, 0); |
2557 | 997 | } |
2558 | | |
2559 | | int MqttClient_Publish_ex(MqttClient *client, MqttPublish *publish, |
2560 | | MqttPublishCb pubCb) |
2561 | 0 | { |
2562 | 0 | return MqttPublishMsg(client, publish, pubCb, 0); |
2563 | 0 | } |
2564 | | |
2565 | | #ifdef WOLFMQTT_MULTITHREAD |
2566 | | int MqttClient_Publish_WriteOnly(MqttClient *client, MqttPublish *publish, |
2567 | | MqttPublishCb pubCb) |
2568 | | { |
2569 | | return MqttPublishMsg(client, publish, pubCb, 1); |
2570 | | } |
2571 | | #endif |
2572 | | |
2573 | | |
2574 | | int MqttClient_Subscribe(MqttClient *client, MqttSubscribe *subscribe) |
2575 | 1.53k | { |
2576 | 1.53k | int rc, i; |
2577 | 1.53k | MqttTopic* topic; |
2578 | | |
2579 | | /* Validate required arguments */ |
2580 | 1.53k | if (client == NULL || subscribe == NULL) { |
2581 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
2582 | 0 | } |
2583 | | |
2584 | 1.53k | #ifdef WOLFMQTT_V5 |
2585 | | /* Use specified protocol version if set */ |
2586 | 1.53k | subscribe->protocol_level = client->protocol_level; |
2587 | 1.53k | #endif |
2588 | | |
2589 | 1.53k | if (subscribe->stat.write == MQTT_MSG_BEGIN) { |
2590 | | /* Flag write active / lock mutex */ |
2591 | 1.53k | if ((rc = MqttWriteStart(client, &subscribe->stat)) != 0) { |
2592 | 0 | return rc; |
2593 | 0 | } |
2594 | | |
2595 | | /* Encode the subscribe packet */ |
2596 | 1.53k | rc = MqttEncode_Subscribe(client->tx_buf, client->tx_buf_len, |
2597 | 1.53k | subscribe); |
2598 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
2599 | | PRINTF("MqttClient_EncodePacket: Len %d, Type %s (%d), ID %d", |
2600 | | rc, MqttPacket_TypeDesc(MQTT_PACKET_TYPE_SUBSCRIBE), |
2601 | | MQTT_PACKET_TYPE_SUBSCRIBE, subscribe->packet_id); |
2602 | | #endif |
2603 | 1.53k | if (rc <= 0) { |
2604 | 1.11k | MqttWriteStop(client, &subscribe->stat); |
2605 | 1.11k | return rc; |
2606 | 1.11k | } |
2607 | 425 | client->write.len = rc; |
2608 | | |
2609 | | #ifdef WOLFMQTT_MULTITHREAD |
2610 | | rc = wm_SemLock(&client->lockClient); |
2611 | | if (rc == 0) { |
2612 | | /* inform other threads of expected response */ |
2613 | | rc = MqttClient_RespList_Add(client, MQTT_PACKET_TYPE_SUBSCRIBE_ACK, |
2614 | | subscribe->packet_id, &subscribe->pendResp, &subscribe->ack); |
2615 | | wm_SemUnlock(&client->lockClient); |
2616 | | } |
2617 | | if (rc != 0) { |
2618 | | MqttWriteStop(client, &subscribe->stat); |
2619 | | return rc; /* Error locking client */ |
2620 | | } |
2621 | | #endif |
2622 | | |
2623 | 425 | subscribe->stat.write = MQTT_MSG_HEADER; |
2624 | 425 | } |
2625 | 425 | if (subscribe->stat.write == MQTT_MSG_HEADER) { |
2626 | 425 | int xfer = client->write.len; |
2627 | | |
2628 | | /* Send subscribe packet */ |
2629 | 425 | rc = MqttPacket_Write(client, client->tx_buf, xfer); |
2630 | | #ifdef WOLFMQTT_NONBLOCK |
2631 | | if (rc == MQTT_CODE_CONTINUE |
2632 | | #ifdef WOLFMQTT_ALLOW_NODATA_UNLOCK |
2633 | | && client->write.total > 0 |
2634 | | #endif |
2635 | | ) { |
2636 | | /* keep send locked and return early */ |
2637 | | return rc; |
2638 | | } |
2639 | | #endif |
2640 | 425 | MqttWriteStop(client, &subscribe->stat); |
2641 | 425 | if (rc != xfer) { |
2642 | 186 | MqttClient_CancelMessage(client, (MqttObject*)subscribe); |
2643 | 186 | return rc; |
2644 | 186 | } |
2645 | | |
2646 | 239 | subscribe->stat.write = MQTT_MSG_WAIT; |
2647 | 239 | } |
2648 | | |
2649 | | /* Wait for subscribe ack packet */ |
2650 | 239 | rc = MqttClient_WaitType(client, &subscribe->ack, |
2651 | 239 | MQTT_PACKET_TYPE_SUBSCRIBE_ACK, subscribe->packet_id, |
2652 | 239 | client->cmd_timeout_ms); |
2653 | | #if defined(WOLFMQTT_NONBLOCK) || defined(WOLFMQTT_MULTITHREAD) |
2654 | | if (rc == MQTT_CODE_CONTINUE) |
2655 | | return rc; |
2656 | | #endif |
2657 | | |
2658 | | #ifdef WOLFMQTT_MULTITHREAD |
2659 | | if (wm_SemLock(&client->lockClient) == 0) { |
2660 | | MqttClient_RespList_Remove(client, &subscribe->pendResp); |
2661 | | wm_SemUnlock(&client->lockClient); |
2662 | | } |
2663 | | #endif |
2664 | | |
2665 | | /* Populate return codes and detect broker rejection. A v3.1.1 SUBACK |
2666 | | * uses MQTT_SUBSCRIBE_ACK_CODE_FAILURE (0x80) to indicate failure; |
2667 | | * a v5 SUBACK uses any reason code >= 0x80. In either case, any |
2668 | | * per-topic code with the high bit set means the broker rejected |
2669 | | * that filter. */ |
2670 | 239 | if (rc == MQTT_CODE_SUCCESS) { |
2671 | 180 | byte any_rejected = 0; |
2672 | | /* [MQTT-3.9.3-1] a SUBACK carries exactly one reason code per |
2673 | | * requested topic; too few would be read as granted QoS 0 (fail-open). */ |
2674 | 180 | if (subscribe->ack.return_code_count != subscribe->topic_count) { |
2675 | 37 | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); |
2676 | 37 | } |
2677 | 143 | else { |
2678 | 360 | for (i = 0; i < subscribe->topic_count && i < MAX_MQTT_TOPICS; i++) { |
2679 | 217 | topic = &subscribe->topics[i]; |
2680 | 217 | topic->return_code = subscribe->ack.return_codes[i]; |
2681 | 217 | if (topic->return_code & MQTT_SUBSCRIBE_ACK_CODE_FAILURE) { |
2682 | 108 | any_rejected = 1; |
2683 | 108 | } |
2684 | 217 | } |
2685 | 143 | if (any_rejected) { |
2686 | 76 | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_SUBSCRIBE_REJECTED); |
2687 | 76 | } |
2688 | 143 | } |
2689 | 180 | } |
2690 | | |
2691 | | /* reset state */ |
2692 | 239 | subscribe->stat.write = MQTT_MSG_BEGIN; |
2693 | | |
2694 | 239 | return rc; |
2695 | 425 | } |
2696 | | |
2697 | | int MqttClient_Unsubscribe(MqttClient *client, MqttUnsubscribe *unsubscribe) |
2698 | 929 | { |
2699 | 929 | int rc; |
2700 | 929 | #ifdef WOLFMQTT_V5 |
2701 | 929 | int i; |
2702 | 929 | word16 reason_count; |
2703 | 929 | #endif |
2704 | | |
2705 | | /* Validate required arguments */ |
2706 | 929 | if (client == NULL || unsubscribe == NULL) { |
2707 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
2708 | 0 | } |
2709 | | |
2710 | 929 | #ifdef WOLFMQTT_V5 |
2711 | | /* Use specified protocol version if set */ |
2712 | 929 | unsubscribe->protocol_level = client->protocol_level; |
2713 | 929 | #endif |
2714 | | |
2715 | 929 | if (unsubscribe->stat.write == MQTT_MSG_BEGIN) { |
2716 | | /* Flag write active / lock mutex */ |
2717 | 929 | if ((rc = MqttWriteStart(client, &unsubscribe->stat)) != 0) { |
2718 | 0 | return rc; |
2719 | 0 | } |
2720 | | |
2721 | | /* Encode the subscribe packet */ |
2722 | 929 | rc = MqttEncode_Unsubscribe(client->tx_buf, client->tx_buf_len, |
2723 | 929 | unsubscribe); |
2724 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
2725 | | PRINTF("MqttClient_EncodePacket: Len %d, Type %s (%d), ID %d, QoS %d", |
2726 | | rc, MqttPacket_TypeDesc(MQTT_PACKET_TYPE_UNSUBSCRIBE), |
2727 | | MQTT_PACKET_TYPE_UNSUBSCRIBE, unsubscribe->packet_id, 0); |
2728 | | #endif |
2729 | 929 | if (rc <= 0) { |
2730 | 651 | MqttWriteStop(client, &unsubscribe->stat); |
2731 | 651 | return rc; |
2732 | 651 | } |
2733 | 278 | client->write.len = rc; |
2734 | | |
2735 | | #ifdef WOLFMQTT_MULTITHREAD |
2736 | | rc = wm_SemLock(&client->lockClient); |
2737 | | if (rc == 0) { |
2738 | | /* inform other threads of expected response */ |
2739 | | rc = MqttClient_RespList_Add(client, |
2740 | | MQTT_PACKET_TYPE_UNSUBSCRIBE_ACK, unsubscribe->packet_id, |
2741 | | &unsubscribe->pendResp, &unsubscribe->ack); |
2742 | | wm_SemUnlock(&client->lockClient); |
2743 | | } |
2744 | | if (rc != 0) { |
2745 | | MqttWriteStop(client, &unsubscribe->stat); |
2746 | | return rc; |
2747 | | } |
2748 | | #endif |
2749 | | |
2750 | 278 | unsubscribe->stat.write = MQTT_MSG_HEADER; |
2751 | 278 | } |
2752 | 278 | if (unsubscribe->stat.write == MQTT_MSG_HEADER) { |
2753 | 278 | int xfer = client->write.len; |
2754 | | |
2755 | | /* Send unsubscribe packet */ |
2756 | 278 | rc = MqttPacket_Write(client, client->tx_buf, xfer); |
2757 | | #ifdef WOLFMQTT_NONBLOCK |
2758 | | if (rc == MQTT_CODE_CONTINUE |
2759 | | #ifdef WOLFMQTT_ALLOW_NODATA_UNLOCK |
2760 | | && client->write.total > 0 |
2761 | | #endif |
2762 | | ) { |
2763 | | /* keep send locked and return early */ |
2764 | | return rc; |
2765 | | } |
2766 | | #endif |
2767 | 278 | MqttWriteStop(client, &unsubscribe->stat); |
2768 | 278 | if (rc != xfer) { |
2769 | 184 | MqttClient_CancelMessage(client, (MqttObject*)unsubscribe); |
2770 | 184 | return rc; |
2771 | 184 | } |
2772 | | |
2773 | 94 | unsubscribe->stat.write = MQTT_MSG_WAIT; |
2774 | 94 | } |
2775 | | |
2776 | | /* Wait for unsubscribe ack packet */ |
2777 | 94 | rc = MqttClient_WaitType(client, &unsubscribe->ack, |
2778 | 94 | MQTT_PACKET_TYPE_UNSUBSCRIBE_ACK, unsubscribe->packet_id, |
2779 | 94 | client->cmd_timeout_ms); |
2780 | | #if defined(WOLFMQTT_NONBLOCK) || defined(WOLFMQTT_MULTITHREAD) |
2781 | | if (rc == MQTT_CODE_CONTINUE) |
2782 | | return rc; |
2783 | | #endif |
2784 | | |
2785 | | #ifdef WOLFMQTT_MULTITHREAD |
2786 | | if (wm_SemLock(&client->lockClient) == 0) { |
2787 | | MqttClient_RespList_Remove(client, &unsubscribe->pendResp); |
2788 | | wm_SemUnlock(&client->lockClient); |
2789 | | } |
2790 | | #endif |
2791 | | |
2792 | 94 | #ifdef WOLFMQTT_V5 |
2793 | | /* Detect broker rejection. A v5 UNSUBACK carries one reason code per |
2794 | | * topic filter; any code with the high bit set (>= 0x80) means the |
2795 | | * broker refused to remove that subscription, so the caller must not |
2796 | | * assume the filter is gone. Mirrors the SUBSCRIBE rejection path. */ |
2797 | 94 | if (rc == MQTT_CODE_SUCCESS && |
2798 | 34 | unsubscribe->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5 && |
2799 | 0 | unsubscribe->ack.reason_codes != NULL) { |
2800 | 0 | reason_count = unsubscribe->ack.reason_code_count; |
2801 | 0 | if (reason_count > (word16)unsubscribe->topic_count) { |
2802 | 0 | reason_count = (word16)unsubscribe->topic_count; |
2803 | 0 | } |
2804 | 0 | for (i = 0; i < (int)reason_count; i++) { |
2805 | 0 | if (unsubscribe->ack.reason_codes[i] & 0x80) { |
2806 | 0 | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_UNSUBSCRIBE_REJECTED); |
2807 | 0 | break; |
2808 | 0 | } |
2809 | 0 | } |
2810 | 0 | } |
2811 | | |
2812 | 94 | if (unsubscribe->ack.props != NULL) { |
2813 | | /* Release the allocated properties and clear the caller-visible |
2814 | | * pointer so a stale reference cannot be observed, reused, or freed |
2815 | | * again after this API returns. */ |
2816 | 0 | MqttClient_PropsFree(unsubscribe->ack.props); |
2817 | 0 | unsubscribe->ack.props = NULL; |
2818 | 0 | } |
2819 | 94 | #endif |
2820 | | |
2821 | | /* reset state */ |
2822 | 94 | unsubscribe->stat.write = MQTT_MSG_BEGIN; |
2823 | | |
2824 | 94 | return rc; |
2825 | 278 | } |
2826 | | |
2827 | | int MqttClient_Ping_ex(MqttClient *client, MqttPing* ping) |
2828 | 1.51k | { |
2829 | 1.51k | int rc; |
2830 | | |
2831 | | /* Validate required arguments */ |
2832 | 1.51k | if (client == NULL || ping == NULL) { |
2833 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
2834 | 0 | } |
2835 | | |
2836 | 1.51k | if (ping->stat.write == MQTT_MSG_BEGIN) { |
2837 | | /* Flag write active / lock mutex */ |
2838 | 1.51k | if ((rc = MqttWriteStart(client, &ping->stat)) != 0) { |
2839 | 0 | return rc; |
2840 | 0 | } |
2841 | | |
2842 | | /* Encode the subscribe packet */ |
2843 | 1.51k | rc = MqttEncode_Ping(client->tx_buf, client->tx_buf_len, ping); |
2844 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
2845 | | PRINTF("MqttClient_EncodePacket: Len %d, Type %s (%d), ID %d, QoS %d", |
2846 | | rc, MqttPacket_TypeDesc(MQTT_PACKET_TYPE_PING_REQ), |
2847 | | MQTT_PACKET_TYPE_PING_REQ, 0, 0); |
2848 | | #endif |
2849 | 1.51k | if (rc <= 0) { |
2850 | 0 | MqttWriteStop(client, &ping->stat); |
2851 | 0 | return rc; |
2852 | 0 | } |
2853 | 1.51k | client->write.len = rc; |
2854 | | |
2855 | | #ifdef WOLFMQTT_MULTITHREAD |
2856 | | rc = wm_SemLock(&client->lockClient); |
2857 | | if (rc == 0) { |
2858 | | /* inform other threads of expected response */ |
2859 | | rc = MqttClient_RespList_Add(client, MQTT_PACKET_TYPE_PING_RESP, 0, |
2860 | | &ping->pendResp, ping); |
2861 | | wm_SemUnlock(&client->lockClient); |
2862 | | } |
2863 | | if (rc != 0) { |
2864 | | MqttWriteStop(client, &ping->stat); |
2865 | | return rc; /* Error locking client */ |
2866 | | } |
2867 | | #endif |
2868 | | |
2869 | 1.51k | ping->stat.write = MQTT_MSG_HEADER; |
2870 | 1.51k | } |
2871 | 1.51k | if (ping->stat.write == MQTT_MSG_HEADER) { |
2872 | 1.51k | int xfer = client->write.len; |
2873 | | |
2874 | | /* Send ping req packet */ |
2875 | 1.51k | rc = MqttPacket_Write(client, client->tx_buf, xfer); |
2876 | | #ifdef WOLFMQTT_NONBLOCK |
2877 | | if (rc == MQTT_CODE_CONTINUE |
2878 | | #ifdef WOLFMQTT_ALLOW_NODATA_UNLOCK |
2879 | | && client->write.total > 0 |
2880 | | #endif |
2881 | | ) { |
2882 | | /* keep send locked and return early */ |
2883 | | return rc; |
2884 | | } |
2885 | | #endif |
2886 | 1.51k | MqttWriteStop(client, &ping->stat); |
2887 | 1.51k | if (rc != xfer) { |
2888 | 189 | MqttClient_CancelMessage(client, (MqttObject*)ping); |
2889 | 189 | return rc; |
2890 | 189 | } |
2891 | | |
2892 | 1.32k | ping->stat.write = MQTT_MSG_WAIT; |
2893 | 1.32k | } |
2894 | | |
2895 | | /* Wait for ping resp packet */ |
2896 | 1.32k | rc = MqttClient_WaitType(client, ping, MQTT_PACKET_TYPE_PING_RESP, 0, |
2897 | 1.32k | client->cmd_timeout_ms); |
2898 | | #if defined(WOLFMQTT_NONBLOCK) || defined(WOLFMQTT_MULTITHREAD) |
2899 | | if (rc == MQTT_CODE_CONTINUE) |
2900 | | return rc; |
2901 | | #endif |
2902 | | |
2903 | | #ifdef WOLFMQTT_MULTITHREAD |
2904 | | if (wm_SemLock(&client->lockClient) == 0) { |
2905 | | MqttClient_RespList_Remove(client, &ping->pendResp); |
2906 | | wm_SemUnlock(&client->lockClient); |
2907 | | } |
2908 | | #endif |
2909 | | |
2910 | | /* reset state */ |
2911 | 1.32k | ping->stat.write = MQTT_MSG_BEGIN; |
2912 | | |
2913 | 1.32k | return rc; |
2914 | 1.51k | } |
2915 | | |
2916 | | int MqttClient_Ping(MqttClient *client) |
2917 | 0 | { |
2918 | 0 | if (client == NULL) { |
2919 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
2920 | 0 | } |
2921 | 0 | return MqttClient_Ping_ex(client, &client->msg.ping); |
2922 | 0 | } |
2923 | | |
2924 | | int MqttClient_Disconnect(MqttClient *client) |
2925 | 0 | { |
2926 | 0 | return MqttClient_Disconnect_ex(client, NULL); |
2927 | 0 | } |
2928 | | |
2929 | | int MqttClient_Disconnect_ex(MqttClient *client, MqttDisconnect *p_disconnect) |
2930 | 0 | { |
2931 | 0 | int rc, xfer; |
2932 | 0 | MqttDisconnect *disconnect = p_disconnect, lcl_disconnect; |
2933 | | |
2934 | | /* Validate required arguments */ |
2935 | 0 | if (client == NULL) { |
2936 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
2937 | 0 | } |
2938 | 0 | if (disconnect == NULL) { |
2939 | 0 | disconnect = &lcl_disconnect; |
2940 | 0 | XMEMSET(disconnect, 0, sizeof(*disconnect)); |
2941 | 0 | } |
2942 | |
|
2943 | 0 | if (disconnect->stat.write == MQTT_MSG_BEGIN) { |
2944 | 0 | #ifndef WOLFMQTT_NO_TIME |
2945 | | /* Stop auto keep-alive for a client being torn down, and fully cancel |
2946 | | * any ping left mid-exchange so its held locks and pending response are |
2947 | | * released before the disconnect write starts. A bare stat reset would |
2948 | | * strand client->write.isActive and livelock MqttClient_Disconnect. |
2949 | | * Propagate a cancel failure - only reachable if wm_SemLock itself |
2950 | | * errors (e.g. on ThreadX), since it otherwise blocks until acquired - |
2951 | | * rather than starting the write with locks the abandoned ping may |
2952 | | * still hold. */ |
2953 | 0 | client->keep_alive_sec = 0; |
2954 | 0 | rc = MqttClient_CancelMessage(client, |
2955 | 0 | (MqttObject*)&client->keep_alive_ping); |
2956 | 0 | if (rc != MQTT_CODE_SUCCESS) { |
2957 | 0 | return rc; |
2958 | 0 | } |
2959 | 0 | #endif |
2960 | 0 | #ifdef WOLFMQTT_V5 |
2961 | | /* Use specified protocol version if set */ |
2962 | 0 | disconnect->protocol_level = client->protocol_level; |
2963 | 0 | #endif |
2964 | | |
2965 | | /* Flag write active / lock mutex */ |
2966 | 0 | if ((rc = MqttWriteStart(client, &disconnect->stat)) != 0) { |
2967 | 0 | return rc; |
2968 | 0 | } |
2969 | | |
2970 | | /* Encode the disconnect packet */ |
2971 | 0 | rc = MqttEncode_Disconnect(client->tx_buf, client->tx_buf_len, |
2972 | 0 | disconnect); |
2973 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
2974 | | PRINTF("MqttClient_EncodePacket: Len %d, Type %s (%d), ID %d, QoS %d", |
2975 | | rc, MqttPacket_TypeDesc(MQTT_PACKET_TYPE_DISCONNECT), |
2976 | | MQTT_PACKET_TYPE_DISCONNECT, 0, 0); |
2977 | | #endif |
2978 | 0 | if (rc <= 0) { |
2979 | | /* Encode failed: tx_buf may hold partial v5 DISCONNECT property |
2980 | | * data. Zero the full buffer before MqttWriteStop releases |
2981 | | * lockSend so no other thread can see residual data. */ |
2982 | 0 | CLIENT_FORCE_ZERO(client->tx_buf, client->tx_buf_len); |
2983 | 0 | MqttWriteStop(client, &disconnect->stat); |
2984 | 0 | return rc; |
2985 | 0 | } |
2986 | 0 | client->write.len = rc; |
2987 | |
|
2988 | 0 | disconnect->stat.write = MQTT_MSG_HEADER; |
2989 | 0 | } |
2990 | | |
2991 | | /* Send disconnect packet */ |
2992 | 0 | xfer = client->write.len; |
2993 | 0 | rc = MqttPacket_Write(client, client->tx_buf, xfer); |
2994 | | #ifdef WOLFMQTT_NONBLOCK |
2995 | | /* if disconnect context avail allow partial write in non-blocking mode */ |
2996 | | if (p_disconnect != NULL && rc == MQTT_CODE_CONTINUE |
2997 | | #ifdef WOLFMQTT_ALLOW_NODATA_UNLOCK |
2998 | | && client->write.total > 0 |
2999 | | #endif |
3000 | | ) { |
3001 | | /* keep send locked and return early (tx_buf still holds property |
3002 | | * data until the write completes) */ |
3003 | | return rc; |
3004 | | } |
3005 | | #endif |
3006 | | /* Clear tx_buf to remove any v5 DISCONNECT property data BEFORE |
3007 | | * MqttWriteStop releases lockSend, so another thread cannot race in and |
3008 | | * populate tx_buf before it is scrubbed. */ |
3009 | 0 | CLIENT_FORCE_ZERO(client->tx_buf, xfer); |
3010 | 0 | MqttWriteStop(client, &disconnect->stat); |
3011 | 0 | if (rc == xfer) { |
3012 | 0 | rc = MQTT_CODE_SUCCESS; |
3013 | 0 | } |
3014 | |
|
3015 | | #if defined(WOLFMQTT_DISCONNECT_CB) && defined(WOLFMQTT_USE_CB_ON_DISCONNECT) |
3016 | | /* Trigger disconnect callback - for intentional disconnect |
3017 | | * This callback may occur on a network failure during an intentional |
3018 | | * disconnect if the transport/socket is not setup yet. */ |
3019 | | if (client->disconnect_cb |
3020 | | #ifdef WOLFMQTT_NONBLOCK |
3021 | | && rc != MQTT_CODE_CONTINUE |
3022 | | #endif |
3023 | | ) { |
3024 | | client->disconnect_cb(client, rc, client->disconnect_ctx); |
3025 | | } |
3026 | | #endif |
3027 | | |
3028 | | /* No response for MQTT disconnect packet */ |
3029 | | |
3030 | | /* reset state */ |
3031 | 0 | disconnect->stat.write = MQTT_MSG_BEGIN; |
3032 | |
|
3033 | 0 | return rc; |
3034 | 0 | } |
3035 | | |
3036 | | #ifdef WOLFMQTT_V5 |
3037 | | int MqttClient_Auth(MqttClient *client, MqttAuth* auth) |
3038 | 0 | { |
3039 | 0 | int rc; |
3040 | | |
3041 | | /* Validate required arguments */ |
3042 | 0 | if (client == NULL || auth == NULL) { |
3043 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
3044 | 0 | } |
3045 | | |
3046 | 0 | if (auth->stat.write == MQTT_MSG_BEGIN) { |
3047 | | /* Flag write active / lock mutex */ |
3048 | 0 | if ((rc = MqttWriteStart(client, &auth->stat)) != 0) { |
3049 | 0 | return rc; |
3050 | 0 | } |
3051 | | |
3052 | | /* Encode the authentication packet */ |
3053 | 0 | rc = MqttEncode_Auth(client->tx_buf, client->tx_buf_len, auth); |
3054 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
3055 | | PRINTF("MqttClient_EncodePacket: Len %d, Type %s (%d), ID %d, QoS %d", |
3056 | | rc, MqttPacket_TypeDesc(MQTT_PACKET_TYPE_AUTH), |
3057 | | MQTT_PACKET_TYPE_AUTH, 0, 0); |
3058 | | #endif |
3059 | 0 | if (rc <= 0) { |
3060 | | /* Encode failed: tx_buf may hold partial SASL auth data. |
3061 | | * Zero the full buffer before MqttWriteStop releases lockSend |
3062 | | * so no other thread can see residual data. */ |
3063 | 0 | CLIENT_FORCE_ZERO(client->tx_buf, client->tx_buf_len); |
3064 | 0 | MqttWriteStop(client, &auth->stat); |
3065 | 0 | return rc; |
3066 | 0 | } |
3067 | 0 | client->write.len = rc; |
3068 | |
|
3069 | | #ifdef WOLFMQTT_MULTITHREAD |
3070 | | rc = wm_SemLock(&client->lockClient); |
3071 | | if (rc == 0) { |
3072 | | /* inform other threads of expected response */ |
3073 | | rc = MqttClient_RespList_Add(client, MQTT_PACKET_TYPE_AUTH, 0, |
3074 | | &auth->pendResp, auth); |
3075 | | wm_SemUnlock(&client->lockClient); |
3076 | | } |
3077 | | if (rc != 0) { |
3078 | | /* Save write.len before MqttWriteStop zeroes client->write */ |
3079 | | int xfer = client->write.len; |
3080 | | /* Clear tx_buf to remove SASL auth data BEFORE MqttWriteStop |
3081 | | * releases lockSend, to prevent a racing thread from |
3082 | | * repopulating tx_buf before it is scrubbed. */ |
3083 | | CLIENT_FORCE_ZERO(client->tx_buf, xfer); |
3084 | | MqttWriteStop(client, &auth->stat); |
3085 | | return rc; /* Error locking client */ |
3086 | | } |
3087 | | #endif |
3088 | |
|
3089 | 0 | auth->stat.write = MQTT_MSG_HEADER; |
3090 | 0 | } |
3091 | 0 | if (auth->stat.write == MQTT_MSG_HEADER) { |
3092 | 0 | int xfer = client->write.len; |
3093 | | |
3094 | | /* Send authentication packet */ |
3095 | 0 | rc = MqttPacket_Write(client, client->tx_buf, xfer); |
3096 | | #ifdef WOLFMQTT_NONBLOCK |
3097 | | if (rc == MQTT_CODE_CONTINUE |
3098 | | #ifdef WOLFMQTT_ALLOW_NODATA_UNLOCK |
3099 | | && client->write.total > 0 |
3100 | | #endif |
3101 | | ) { |
3102 | | /* keep send locked and return early */ |
3103 | | return rc; |
3104 | | } |
3105 | | #endif |
3106 | | /* Clear tx_buf to remove any SASL auth data from memory BEFORE |
3107 | | * MqttWriteStop releases lockSend, to prevent a racing thread |
3108 | | * from populating tx_buf before it is scrubbed. |
3109 | | * Use xfer (saved before MqttWriteStop zeroes client->write). */ |
3110 | 0 | CLIENT_FORCE_ZERO(client->tx_buf, xfer); |
3111 | 0 | MqttWriteStop(client, &auth->stat); |
3112 | |
|
3113 | 0 | if (rc != xfer) { |
3114 | 0 | MqttClient_CancelMessage(client, (MqttObject*)auth); |
3115 | 0 | return rc; |
3116 | 0 | } |
3117 | | |
3118 | 0 | auth->stat.write = MQTT_MSG_WAIT; |
3119 | 0 | } |
3120 | | |
3121 | | /* Wait for auth packet */ |
3122 | 0 | rc = MqttClient_WaitType(client, auth, MQTT_PACKET_TYPE_AUTH, 0, |
3123 | 0 | client->cmd_timeout_ms); |
3124 | | #if defined(WOLFMQTT_NONBLOCK) || defined(WOLFMQTT_MULTITHREAD) |
3125 | | if (rc == MQTT_CODE_CONTINUE) |
3126 | | return rc; |
3127 | | #endif |
3128 | |
|
3129 | | #ifdef WOLFMQTT_MULTITHREAD |
3130 | | if (wm_SemLock(&client->lockClient) == 0) { |
3131 | | MqttClient_RespList_Remove(client, &auth->pendResp); |
3132 | | wm_SemUnlock(&client->lockClient); |
3133 | | } |
3134 | | #endif |
3135 | | |
3136 | | /* Scrub the decoded AUTH response from rx_buf. Its properties (e.g. the |
3137 | | * MQTT_PROP_AUTH_DATA SASL blob) point into rx_buf and would otherwise |
3138 | | * linger until the next read overwrites them. MqttClient_WaitType above |
3139 | | * already delivered and freed auth->props, so the bytes are consumed |
3140 | | * before this scrub and the caller has no live pointer into rx_buf. */ |
3141 | | #ifdef WOLFMQTT_MULTITHREAD |
3142 | | /* Hold lockRecv so the scrub cannot race a concurrent read into rx_buf. If |
3143 | | * the lock cannot be taken, still scrub: leaving the AUTH_DATA plaintext |
3144 | | * behind is worse than an unsynchronized wipe. */ |
3145 | | if (wm_SemLock(&client->lockRecv) == 0) { |
3146 | | CLIENT_FORCE_ZERO(client->rx_buf, client->rx_buf_len); |
3147 | | wm_SemUnlock(&client->lockRecv); |
3148 | | } |
3149 | | else { |
3150 | | CLIENT_FORCE_ZERO(client->rx_buf, client->rx_buf_len); |
3151 | | } |
3152 | | #else |
3153 | 0 | CLIENT_FORCE_ZERO(client->rx_buf, client->rx_buf_len); |
3154 | 0 | #endif |
3155 | | |
3156 | | /* reset state */ |
3157 | 0 | auth->stat.write = MQTT_MSG_BEGIN; |
3158 | |
|
3159 | 0 | return rc; |
3160 | 0 | } |
3161 | | |
3162 | | MqttProp* MqttClient_PropsAdd(MqttProp **head) |
3163 | 0 | { |
3164 | 0 | return MqttProps_Add(head); |
3165 | 0 | } |
3166 | | |
3167 | | int MqttClient_PropsFree(MqttProp *head) |
3168 | 0 | { |
3169 | 0 | return MqttProps_Free(head); |
3170 | 0 | } |
3171 | | |
3172 | | #endif /* WOLFMQTT_V5 */ |
3173 | | |
3174 | | #ifndef WOLFMQTT_NO_TIME |
3175 | | /* Send a keep-alive PINGREQ when the outbound link has been idle for about |
3176 | | * three quarters of the negotiated keep-alive interval, so the application |
3177 | | * does not have to schedule pings itself. Called from the wait path. |
3178 | | * [MQTT-3.1.2-23] */ |
3179 | | static int MqttClient_KeepAlive(MqttClient *client, MqttObject* msg) |
3180 | 6.45k | { |
3181 | 6.45k | int rc = MQTT_CODE_SUCCESS; |
3182 | 6.45k | int mid_transfer; |
3183 | 6.45k | word32 now, elapsed, threshold; |
3184 | | |
3185 | 6.45k | if (client == NULL) { |
3186 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
3187 | 0 | } |
3188 | | |
3189 | | /* Disabled at runtime by the application (e.g. it schedules its own ping), |
3190 | | * independent of the keep-alive negotiated with the broker. */ |
3191 | 6.45k | if ((client->flags & MQTT_CLIENT_FLAG_NO_AUTO_KEEPALIVE) != 0) { |
3192 | 0 | return MQTT_CODE_SUCCESS; |
3193 | 0 | } |
3194 | | |
3195 | | /* Disabled until a non-zero keep-alive has been negotiated in CONNECT. */ |
3196 | 6.45k | if (client->keep_alive_sec == 0) { |
3197 | 6.32k | return MQTT_CODE_SUCCESS; |
3198 | 6.32k | } |
3199 | | |
3200 | | /* Resume an in-progress ping exchange before evaluating the threshold. In |
3201 | | * WOLFMQTT_NONBLOCK mode MqttClient_Ping_ex can return MQTT_CODE_CONTINUE |
3202 | | * before the PINGRESP arrives, leaving the ping state machine mid-exchange. |
3203 | | * Drive it to completion here so a later ping is not entered with stale |
3204 | | * state, which would skip the PINGREQ and stretch the real ping interval. */ |
3205 | 132 | if (client->keep_alive_ping.stat.write != MQTT_MSG_BEGIN) { |
3206 | 0 | rc = MqttClient_Ping_ex(client, &client->keep_alive_ping); |
3207 | 0 | if (rc == MQTT_CODE_ERROR_TIMEOUT) { |
3208 | 0 | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_NETWORK); |
3209 | 0 | } |
3210 | 0 | return rc; |
3211 | 0 | } |
3212 | | |
3213 | | /* Do not inject a ping while a message is mid-transfer: a partially read |
3214 | | * fixed header (packet.stat), or a payload read still in progress on the |
3215 | | * wait object (msg->read), which packet.stat alone does not catch once the |
3216 | | * header has been consumed but the read is not finished. Under |
3217 | | * WOLFMQTT_MULTITHREAD client->packet and client->read are protected by the |
3218 | | * read lock, so evaluate this under lockClient (which MqttReadStart and |
3219 | | * MqttReadStop also hold when they set read.isActive and reset |
3220 | | * packet.stat). read.isActive - an in-progress read holding lockRecv - is |
3221 | | * tested first, so packet.stat is only read when no read is active and thus |
3222 | | * cannot be concurrently advanced. wm_SemLock blocks until lockClient is |
3223 | | * available and returns an error only on a system fault (e.g. ThreadX), in |
3224 | | * which case the ping is skipped this round. */ |
3225 | 132 | mid_transfer = 0; |
3226 | | #ifdef WOLFMQTT_MULTITHREAD |
3227 | | if (wm_SemLock(&client->lockClient) == 0) { |
3228 | | if (client->read.isActive || |
3229 | | client->packet.stat != MQTT_PK_BEGIN || |
3230 | | (msg != NULL && ((MqttMsgStat*)msg)->read != MQTT_MSG_BEGIN)) { |
3231 | | mid_transfer = 1; |
3232 | | } |
3233 | | wm_SemUnlock(&client->lockClient); |
3234 | | } |
3235 | | else { |
3236 | | mid_transfer = 1; |
3237 | | } |
3238 | | #else |
3239 | 132 | if (client->packet.stat != MQTT_PK_BEGIN || |
3240 | 98 | (msg != NULL && ((MqttMsgStat*)msg)->read != MQTT_MSG_BEGIN)) { |
3241 | 78 | mid_transfer = 1; |
3242 | 78 | } |
3243 | 132 | #endif |
3244 | 132 | if (mid_transfer) { |
3245 | 78 | return MQTT_CODE_SUCCESS; |
3246 | 78 | } |
3247 | | |
3248 | 54 | now = WOLFMQTT_GET_TIME_S(); |
3249 | 54 | if (now < client->last_tx_time) { |
3250 | | /* Clock stepped backward: re-baseline instead of pinging early. */ |
3251 | 0 | client->last_tx_time = now; |
3252 | 0 | return MQTT_CODE_SUCCESS; |
3253 | 0 | } |
3254 | | |
3255 | | /* Ping at ~3/4 of the interval so the PINGREQ reaches the broker before |
3256 | | * the hard deadline, leaving headroom for network latency and the |
3257 | | * one-second clock granularity. Floor at one second so a small keep-alive |
3258 | | * still schedules a single ping instead of firing on every poll. */ |
3259 | 54 | threshold = (word32)client->keep_alive_sec * 3 / 4; |
3260 | 54 | if (threshold == 0) { |
3261 | 54 | threshold = 1; |
3262 | 54 | } |
3263 | | |
3264 | 54 | elapsed = now - client->last_tx_time; |
3265 | 54 | if (elapsed >= threshold) { |
3266 | | /* MqttPacket_Write refreshes last_tx_time as the PINGREQ is sent. */ |
3267 | 0 | rc = MqttClient_Ping_ex(client, &client->keep_alive_ping); |
3268 | 0 | if (rc == MQTT_CODE_ERROR_TIMEOUT) { |
3269 | | /* No PINGRESP within cmd_timeout_ms: the link is unresponsive, not |
3270 | | * merely idle. Surface a distinct error so a caller does not treat |
3271 | | * a failed keep-alive as an ordinary read timeout and keep looping |
3272 | | * on a dead connection. */ |
3273 | 0 | rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_NETWORK); |
3274 | 0 | } |
3275 | 0 | } |
3276 | 54 | return rc; |
3277 | 54 | } |
3278 | | #endif /* !WOLFMQTT_NO_TIME */ |
3279 | | |
3280 | | int MqttClient_WaitMessage_ex(MqttClient *client, MqttObject* msg, |
3281 | | int timeout_ms) |
3282 | 6.45k | { |
3283 | 6.45k | #ifndef WOLFMQTT_NO_TIME |
3284 | 6.45k | int rc; |
3285 | 6.45k | #endif |
3286 | | |
3287 | 6.45k | if (client == NULL || msg == NULL) { |
3288 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
3289 | 0 | } |
3290 | | |
3291 | 6.45k | #ifndef WOLFMQTT_NO_TIME |
3292 | | /* Send an automatic keep-alive PINGREQ if the outbound link has been idle |
3293 | | * long enough. A deferred ping (another thread holds the write lock) comes |
3294 | | * back as MQTT_CODE_CONTINUE, which is not an error, so in a blocking build |
3295 | | * fall through to the normal wait rather than returning it to the caller. */ |
3296 | 6.45k | rc = MqttClient_KeepAlive(client, msg); |
3297 | 6.45k | if (rc != MQTT_CODE_SUCCESS |
3298 | 0 | #ifndef WOLFMQTT_NONBLOCK |
3299 | 0 | && rc != MQTT_CODE_CONTINUE |
3300 | 6.45k | #endif |
3301 | 6.45k | ) { |
3302 | 0 | return rc; |
3303 | 0 | } |
3304 | 6.45k | #endif |
3305 | 6.45k | return MqttClient_WaitType(client, msg, MQTT_PACKET_TYPE_ANY, 0, |
3306 | 6.45k | timeout_ms); |
3307 | 6.45k | } |
3308 | | int MqttClient_WaitMessage(MqttClient *client, int timeout_ms) |
3309 | 6.45k | { |
3310 | 6.45k | if (client == NULL) |
3311 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
3312 | 6.45k | return MqttClient_WaitMessage_ex(client, &client->msg, timeout_ms); |
3313 | 6.45k | } |
3314 | | |
3315 | | #if !defined(WOLFMQTT_MULTITHREAD) && !defined(WOLFMQTT_NONBLOCK) |
3316 | | static |
3317 | | #endif |
3318 | | int MqttClient_CancelMessage(MqttClient *client, MqttObject* msg) |
3319 | 8.65k | { |
3320 | 8.65k | int rc = MQTT_CODE_SUCCESS; |
3321 | 8.65k | MqttMsgStat* mms_stat; |
3322 | | #ifdef WOLFMQTT_MULTITHREAD |
3323 | | MqttPendResp* tmpResp; |
3324 | | #endif |
3325 | | |
3326 | 8.65k | if (client == NULL || msg == NULL) { |
3327 | 0 | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
3328 | 0 | } |
3329 | | |
3330 | | /* all packet type structures must have MqttMsgStat at top */ |
3331 | 8.65k | mms_stat = (MqttMsgStat*)msg; |
3332 | | |
3333 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
3334 | | PRINTF("Cancel Msg: %p", msg); |
3335 | | #endif |
3336 | | |
3337 | | /* reset states */ |
3338 | 8.65k | mms_stat->write = MQTT_MSG_BEGIN; |
3339 | 8.65k | mms_stat->read = MQTT_MSG_BEGIN; |
3340 | | |
3341 | | #ifdef WOLFMQTT_MULTITHREAD |
3342 | | /* Remove any pending responses expected */ |
3343 | | rc = wm_SemLock(&client->lockClient); |
3344 | | if (rc != MQTT_CODE_SUCCESS) { |
3345 | | return rc; |
3346 | | } |
3347 | | |
3348 | | for (tmpResp = client->firstPendResp; |
3349 | | tmpResp != NULL; |
3350 | | tmpResp = tmpResp->next) |
3351 | | { |
3352 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
3353 | | PRINTF("\tMsg: %p (obj %p), Type %s (%d), ID %d, InProc %d, Done %d", |
3354 | | tmpResp, tmpResp->packet_obj, |
3355 | | MqttPacket_TypeDesc(tmpResp->packet_type), |
3356 | | tmpResp->packet_type, tmpResp->packet_id, |
3357 | | tmpResp->packetProcessing, tmpResp->packetDone); |
3358 | | #endif |
3359 | | if ((size_t)tmpResp->packet_obj == (size_t)msg || |
3360 | | (size_t)tmpResp - OFFSETOF(MqttMessage, pendResp) == (size_t)msg) { |
3361 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
3362 | | PRINTF("Found Cancel Msg: %p (obj %p), Type %s (%d), ID %d, " |
3363 | | "InProc %d, Done %d", |
3364 | | tmpResp, tmpResp->packet_obj, |
3365 | | MqttPacket_TypeDesc(tmpResp->packet_type), |
3366 | | tmpResp->packet_type, tmpResp->packet_id, |
3367 | | tmpResp->packetProcessing, tmpResp->packetDone); |
3368 | | #endif |
3369 | | MqttClient_RespList_Remove(client, tmpResp); |
3370 | | break; |
3371 | | } |
3372 | | } |
3373 | | wm_SemUnlock(&client->lockClient); |
3374 | | #endif /* WOLFMQTT_MULTITHREAD */ |
3375 | | |
3376 | | /* cancel any active flags / locks */ |
3377 | 8.65k | if (mms_stat->isReadActive) { |
3378 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
3379 | | PRINTF("Cancel Read Lock"); |
3380 | | #endif |
3381 | 0 | MqttReadStop(client, mms_stat); |
3382 | 0 | } |
3383 | 8.65k | if (mms_stat->isWriteActive) { |
3384 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
3385 | | PRINTF("Cancel Write Lock"); |
3386 | | #endif |
3387 | | /* An abandoned write (e.g. a partial nonblocking CONNECT) leaves the |
3388 | | * encoded packet - possibly plaintext credentials - in tx_buf. Scrub |
3389 | | * it before MqttWriteStop releases lockSend. */ |
3390 | 0 | CLIENT_FORCE_ZERO(client->tx_buf, client->tx_buf_len); |
3391 | 0 | MqttWriteStop(client, mms_stat); |
3392 | 0 | } |
3393 | | |
3394 | 8.65k | return rc; |
3395 | 8.65k | } |
3396 | | |
3397 | | #ifdef WOLFMQTT_NONBLOCK |
3398 | | static inline int IsMessageActive(MqttObject *msg) |
3399 | | { |
3400 | | return (msg->stat.read != MQTT_MSG_BEGIN || |
3401 | | msg->stat.write != MQTT_MSG_BEGIN); |
3402 | | } |
3403 | | |
3404 | | int MqttClient_IsMessageActive( |
3405 | | MqttClient *client, |
3406 | | MqttObject *msg) |
3407 | | { |
3408 | | int rc; |
3409 | | |
3410 | | /* must supply either client or msg */ |
3411 | | if (client == NULL && msg == NULL) { |
3412 | | return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); |
3413 | | } |
3414 | | |
3415 | | /* if msg is null then client->msg is used */ |
3416 | | if ((client != NULL && &client->msg == msg) || msg == NULL) { |
3417 | | #ifdef WOLFMQTT_MULTITHREAD |
3418 | | rc = wm_SemLock(&client->lockClient); |
3419 | | if (rc == 0) |
3420 | | #endif |
3421 | | { |
3422 | | rc = IsMessageActive(&client->msg); |
3423 | | #ifdef WOLFMQTT_MULTITHREAD |
3424 | | wm_SemUnlock(&client->lockClient); |
3425 | | #endif |
3426 | | } |
3427 | | } |
3428 | | else { |
3429 | | rc = IsMessageActive(msg); |
3430 | | } |
3431 | | return rc; |
3432 | | } |
3433 | | |
3434 | | |
3435 | | #endif /* WOLFMQTT_NONBLOCK */ |
3436 | | |
3437 | | |
3438 | | int MqttClient_NetConnect(MqttClient *client, const char* host, |
3439 | | word16 port, int timeout_ms, int use_tls, MqttTlsCb cb) |
3440 | 4.61k | { |
3441 | 4.61k | return MqttSocket_Connect(client, host, port, timeout_ms, use_tls, cb); |
3442 | 4.61k | } |
3443 | | |
3444 | | int MqttClient_NetDisconnect(MqttClient *client) |
3445 | 138 | { |
3446 | | #ifdef WOLFMQTT_MULTITHREAD |
3447 | | MqttPendResp *tmpResp; |
3448 | | MqttPendResp *nextResp; |
3449 | | int rc; |
3450 | | #endif |
3451 | | |
3452 | 138 | if (client == NULL) { |
3453 | 0 | return MQTT_CODE_ERROR_BAD_ARG; |
3454 | 0 | } |
3455 | | |
3456 | | #ifdef WOLFMQTT_MULTITHREAD |
3457 | | /* Get client lock on to ensure no other threads are active */ |
3458 | | rc = wm_SemLock(&client->lockClient); |
3459 | | if (rc == 0) { |
3460 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
3461 | | PRINTF("Net Disconnect: Removing pending responses"); |
3462 | | #endif |
3463 | | for (tmpResp = client->firstPendResp; |
3464 | | tmpResp != NULL; |
3465 | | tmpResp = nextResp) { |
3466 | | nextResp = tmpResp->next; |
3467 | | #ifdef WOLFMQTT_DEBUG_CLIENT |
3468 | | PRINTF("\tPendResp: %p (obj %p), Type %s (%d), ID %d, InProc %d, Done %d", |
3469 | | tmpResp, tmpResp->packet_obj, |
3470 | | MqttPacket_TypeDesc(tmpResp->packet_type), |
3471 | | tmpResp->packet_type, tmpResp->packet_id, |
3472 | | tmpResp->packetProcessing, tmpResp->packetDone); |
3473 | | #endif |
3474 | | MqttClient_RespList_Remove(client, tmpResp); |
3475 | | } |
3476 | | wm_SemUnlock(&client->lockClient); |
3477 | | } |
3478 | | else { |
3479 | | return rc; |
3480 | | } |
3481 | | #endif |
3482 | | |
3483 | 138 | return MqttSocket_Disconnect(client); |
3484 | 138 | } |
3485 | | |
3486 | | int MqttClient_GetProtocolVersion(MqttClient *client) |
3487 | 0 | { |
3488 | 0 | #ifdef WOLFMQTT_V5 |
3489 | 0 | if (client && client->protocol_level == MQTT_CONNECT_PROTOCOL_LEVEL_5) |
3490 | 0 | return MQTT_CONNECT_PROTOCOL_LEVEL_5; |
3491 | | #else |
3492 | | (void)client; |
3493 | | #endif |
3494 | 0 | return MQTT_CONNECT_PROTOCOL_LEVEL_4; |
3495 | 0 | } |
3496 | | const char* MqttClient_GetProtocolVersionString(MqttClient *client) |
3497 | 0 | { |
3498 | 0 | const char* str = NULL; |
3499 | 0 | int ver = MqttClient_GetProtocolVersion(client); |
3500 | 0 | switch (ver) { |
3501 | 0 | case MQTT_CONNECT_PROTOCOL_LEVEL_4: |
3502 | 0 | return "v3.1.1"; |
3503 | 0 | #ifdef WOLFMQTT_V5 |
3504 | 0 | case MQTT_CONNECT_PROTOCOL_LEVEL_5: |
3505 | 0 | return "v5"; |
3506 | 0 | #endif |
3507 | 0 | default: |
3508 | 0 | break; |
3509 | 0 | } |
3510 | 0 | return str; |
3511 | 0 | } |
3512 | | |
3513 | | #ifndef WOLFMQTT_NO_ERROR_STRINGS |
3514 | | const char* MqttClient_ReturnCodeToString(int return_code) |
3515 | 0 | { |
3516 | 0 | switch(return_code) { |
3517 | 0 | case MQTT_CODE_SUCCESS: |
3518 | 0 | return "Success"; |
3519 | 0 | case MQTT_CODE_CONTINUE: |
3520 | 0 | return "Continue"; /* would block */ |
3521 | 0 | case MQTT_CODE_STDIN_WAKE: |
3522 | 0 | return "STDIN Wake"; |
3523 | 0 | case MQTT_CODE_PUB_CONTINUE: |
3524 | 0 | return "Continue calling publish"; /* Chunked publish */ |
3525 | 0 | case MQTT_CODE_ERROR_BAD_ARG: |
3526 | 0 | return "Error (Bad argument)"; |
3527 | 0 | case MQTT_CODE_ERROR_OUT_OF_BUFFER: |
3528 | 0 | return "Error (Out of buffer)"; |
3529 | 0 | case MQTT_CODE_ERROR_MALFORMED_DATA: |
3530 | 0 | return "Error (Malformed Remaining Length)"; |
3531 | 0 | case MQTT_CODE_ERROR_PACKET_TYPE: |
3532 | 0 | return "Error (Packet Type Mismatch)"; |
3533 | 0 | case MQTT_CODE_ERROR_PACKET_ID: |
3534 | 0 | return "Error (Packet Id Mismatch)"; |
3535 | 0 | case MQTT_CODE_ERROR_TLS_CONNECT: |
3536 | 0 | return "Error (TLS Connect)"; |
3537 | 0 | case MQTT_CODE_ERROR_TIMEOUT: |
3538 | 0 | return "Error (Timeout)"; |
3539 | 0 | case MQTT_CODE_ERROR_NETWORK: |
3540 | 0 | return "Error (Network)"; |
3541 | 0 | case MQTT_CODE_ERROR_MEMORY: |
3542 | 0 | return "Error (Memory)"; |
3543 | 0 | case MQTT_CODE_ERROR_STAT: |
3544 | 0 | return "Error (State)"; |
3545 | 0 | case MQTT_CODE_ERROR_PROPERTY: |
3546 | 0 | return "Error (Property)"; |
3547 | 0 | case MQTT_CODE_ERROR_SERVER_PROP: |
3548 | 0 | return "Error (Server Property)"; |
3549 | 0 | case MQTT_CODE_ERROR_CALLBACK: |
3550 | 0 | return "Error (Error in Callback)"; |
3551 | 0 | case MQTT_CODE_ERROR_SYSTEM: |
3552 | 0 | return "Error (System resource failed)"; |
3553 | 0 | case MQTT_CODE_ERROR_NOT_FOUND: |
3554 | 0 | return "Error (Not found)"; |
3555 | 0 | case MQTT_CODE_ERROR_CONNECT_REFUSED: |
3556 | 0 | return "Error (Broker refused connection)"; |
3557 | 0 | case MQTT_CODE_ERROR_SUBSCRIBE_REJECTED: |
3558 | 0 | return "Error (Broker rejected subscription)"; |
3559 | 0 | case MQTT_CODE_ERROR_UNSUBSCRIBE_REJECTED: |
3560 | 0 | return "Error (Broker rejected unsubscribe)"; |
3561 | 0 | case MQTT_CODE_ERROR_PUBLISH_REJECTED: |
3562 | 0 | return "Error (Broker rejected publish)"; |
3563 | | #if defined(ENABLE_MQTT_CURL) |
3564 | | case MQTT_CODE_ERROR_CURL: |
3565 | | return "Error (libcurl)"; |
3566 | | #endif |
3567 | | |
3568 | 0 | #ifdef WOLFMQTT_V5 |
3569 | | /* MQTT v5 Reason code strings */ |
3570 | 0 | case MQTT_REASON_UNSPECIFIED_ERR: |
3571 | 0 | return "Unspecified error"; |
3572 | 0 | case MQTT_REASON_MALFORMED_PACKET: |
3573 | 0 | return "Malformed Packet"; |
3574 | 0 | case MQTT_REASON_PROTOCOL_ERR: |
3575 | 0 | return "Protocol Error"; |
3576 | 0 | case MQTT_REASON_IMPL_SPECIFIC_ERR: |
3577 | 0 | return "Implementation specific error"; |
3578 | 0 | case MQTT_REASON_UNSUP_PROTO_VER: |
3579 | 0 | return "Unsupported Protocol Version"; |
3580 | 0 | case MQTT_REASON_CLIENT_ID_NOT_VALID: |
3581 | 0 | return "Client Identifier not valid"; |
3582 | 0 | case MQTT_REASON_BAD_USER_OR_PASS: |
3583 | 0 | return "Bad User Name or Password"; |
3584 | 0 | case MQTT_REASON_NOT_AUTHORIZED: |
3585 | 0 | return "Not authorized"; |
3586 | 0 | case MQTT_REASON_SERVER_UNAVAILABLE: |
3587 | 0 | return "Server unavailable"; |
3588 | 0 | case MQTT_REASON_SERVER_BUSY: |
3589 | 0 | return "Server busy"; |
3590 | 0 | case MQTT_REASON_BANNED: |
3591 | 0 | return "Banned"; |
3592 | 0 | case MQTT_REASON_SERVER_SHUTTING_DOWN: |
3593 | 0 | return "Server shutting down"; |
3594 | 0 | case MQTT_REASON_BAD_AUTH_METHOD: |
3595 | 0 | return "Bad authentication method"; |
3596 | 0 | case MQTT_REASON_KEEP_ALIVE_TIMEOUT: |
3597 | 0 | return "Keep Alive timeout"; |
3598 | 0 | case MQTT_REASON_SESSION_TAKEN_OVER: |
3599 | 0 | return "Session taken over"; |
3600 | 0 | case MQTT_REASON_TOPIC_FILTER_INVALID: |
3601 | 0 | return "Topic Filter invalid"; |
3602 | 0 | case MQTT_REASON_TOPIC_NAME_INVALID: |
3603 | 0 | return "Topic Name invalid"; |
3604 | 0 | case MQTT_REASON_PACKET_ID_IN_USE: |
3605 | 0 | return "Packet Identifier in use"; |
3606 | 0 | case MQTT_REASON_PACKET_ID_NOT_FOUND: |
3607 | 0 | return "Packet Identifier not found"; |
3608 | 0 | case MQTT_REASON_RX_MAX_EXCEEDED: |
3609 | 0 | return "Receive Maximum exceeded"; |
3610 | 0 | case MQTT_REASON_TOPIC_ALIAS_INVALID: |
3611 | 0 | return "Topic Alias invalid"; |
3612 | 0 | case MQTT_REASON_PACKET_TOO_LARGE: |
3613 | 0 | return "Packet too large"; |
3614 | 0 | case MQTT_REASON_MSG_RATE_TOO_HIGH: |
3615 | 0 | return "Message rate too high"; |
3616 | 0 | case MQTT_REASON_QUOTA_EXCEEDED: |
3617 | 0 | return "Quota exceeded"; |
3618 | 0 | case MQTT_REASON_ADMIN_ACTION: |
3619 | 0 | return "Administrative action"; |
3620 | 0 | case MQTT_REASON_PAYLOAD_FORMAT_INVALID: |
3621 | 0 | return "Payload format invalid"; |
3622 | 0 | case MQTT_REASON_RETAIN_NOT_SUPPORTED: |
3623 | 0 | return "Retain not supported"; |
3624 | 0 | case MQTT_REASON_QOS_NOT_SUPPORTED: |
3625 | 0 | return "QoS not supported"; |
3626 | 0 | case MQTT_REASON_USE_ANOTHER_SERVER: |
3627 | 0 | return "Use another server"; |
3628 | 0 | case MQTT_REASON_SERVER_MOVED: |
3629 | 0 | return "Server moved"; |
3630 | 0 | case MQTT_REASON_SS_NOT_SUPPORTED: |
3631 | 0 | return "Shared Subscriptions not supported"; |
3632 | 0 | case MQTT_REASON_CON_RATE_EXCEED: |
3633 | 0 | return "Connection rate exceeded"; |
3634 | 0 | case MQTT_REASON_MAX_CON_TIME: |
3635 | 0 | return "Maximum connect time"; |
3636 | 0 | case MQTT_REASON_SUB_ID_NOT_SUP: |
3637 | 0 | return "Subscription Identifiers not supported"; |
3638 | 0 | case MQTT_REASON_WILDCARD_SUB_NOT_SUP: |
3639 | 0 | return "Wildcard Subscriptions not supported"; |
3640 | 0 | #endif |
3641 | 0 | } |
3642 | 0 | return "Unknown"; |
3643 | 0 | } |
3644 | | #endif /* !WOLFMQTT_NO_ERROR_STRINGS */ |
3645 | | |
3646 | | word32 MqttClient_Flags(MqttClient *client, word32 mask, word32 flags) |
3647 | 52.8k | { |
3648 | 52.8k | word32 ret = 0; |
3649 | 52.8k | if (client != NULL) { |
3650 | | #ifdef WOLFMQTT_MULTITHREAD |
3651 | | /* Get client lock on to ensure no other threads are active */ |
3652 | | if (wm_SemLock(&client->lockClient) == 0) |
3653 | | #endif |
3654 | 52.8k | { |
3655 | 52.8k | client->flags &= ~mask; |
3656 | 52.8k | client->flags |= flags; |
3657 | 52.8k | ret = client->flags; |
3658 | | #ifdef WOLFMQTT_MULTITHREAD |
3659 | | wm_SemUnlock(&client->lockClient); |
3660 | | #endif |
3661 | 52.8k | } |
3662 | 52.8k | } |
3663 | 52.8k | return ret; |
3664 | 52.8k | } |