Coverage Report

Created: 2026-07-25 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mosquitto/src/loop.c
Line
Count
Source
1
/*
2
Copyright (c) 2009-2021 Roger Light <roger@atchoo.org>
3
4
All rights reserved. This program and the accompanying materials
5
are made available under the terms of the Eclipse Public License 2.0
6
and Eclipse Distribution License v1.0 which accompany this distribution.
7
8
The Eclipse Public License is available at
9
   https://www.eclipse.org/legal/epl-2.0/
10
and the Eclipse Distribution License is available at
11
  http://www.eclipse.org/org/documents/edl-v10.php.
12
13
SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
14
15
Contributors:
16
   Roger Light - initial implementation and documentation.
17
   Tatsuzo Osawa - Add epoll.
18
*/
19
20
#include "config.h"
21
22
#ifndef WIN32
23
#  define _GNU_SOURCE
24
#endif
25
26
#ifndef WIN32
27
#include <unistd.h>
28
#else
29
#include <process.h>
30
#include <winsock2.h>
31
#include <ws2tcpip.h>
32
#endif
33
34
#include <errno.h>
35
#include <signal.h>
36
#include <stdio.h>
37
#include <string.h>
38
#ifndef WIN32
39
#  include <sys/socket.h>
40
#endif
41
#include <time.h>
42
#include <utlist.h>
43
44
#if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_LWS
45
#  include <libwebsockets.h>
46
#endif
47
48
#include "mosquitto_broker_internal.h"
49
#include "mosquitto/mqtt_protocol.h"
50
#include "packet_mosq.h"
51
#include "property_common.h"
52
#include "send_mosq.h"
53
#include "sys_tree.h"
54
#include "util_mosq.h"
55
56
extern int g_run;
57
58
#if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_LWS && LWS_LIBRARY_VERSION_NUMBER == 3002000
59
60
61
void lws__sul_callback(struct lws_sorted_usec_list *l)
62
{
63
}
64
65
static struct lws_sorted_usec_list sul;
66
#endif
67
68
69
static int single_publish(struct mosquitto *context, struct mosquitto__message_v5 *pub_msg, uint32_t message_expiry)
70
0
{
71
0
  struct mosquitto__base_msg *base_msg;
72
0
  uint16_t mid;
73
74
0
  base_msg = mosquitto_calloc(1, sizeof(struct mosquitto__base_msg));
75
0
  if(base_msg == NULL){
76
0
    return MOSQ_ERR_NOMEM;
77
0
  }
78
79
0
  base_msg->data.topic = pub_msg->topic;
80
0
  pub_msg->topic = NULL;
81
0
  base_msg->data.retain = 0;
82
0
  base_msg->data.payloadlen = (uint32_t)pub_msg->payloadlen;
83
0
  base_msg->data.payload = mosquitto_malloc(base_msg->data.payloadlen+1);
84
0
  if(base_msg->data.payload == NULL){
85
0
    db__msg_store_free(base_msg);
86
0
    return MOSQ_ERR_NOMEM;
87
0
  }
88
  /* Ensure payload is always zero terminated, this is the reason for the extra byte above */
89
0
  ((uint8_t *)base_msg->data.payload)[base_msg->data.payloadlen] = 0;
90
0
  memcpy(base_msg->data.payload, pub_msg->payload, base_msg->data.payloadlen);
91
92
0
  if(pub_msg->properties){
93
0
    base_msg->data.properties = pub_msg->properties;
94
0
    pub_msg->properties = NULL;
95
0
  }
96
97
0
  if(db__message_store(context, base_msg, &message_expiry, mosq_mo_broker)){
98
0
    return 1;
99
0
  }
100
101
0
  if(pub_msg->qos){
102
0
    mid = mosquitto__mid_generate(context);
103
0
  }else{
104
0
    mid = 0;
105
0
  }
106
0
  return db__message_insert_outgoing(context, 0, mid, (uint8_t)pub_msg->qos, 0, base_msg, 0, true, true);
107
0
}
108
109
110
static void read_message_expiry_interval(mosquitto_property **proplist, uint32_t *message_expiry)
111
0
{
112
0
  mosquitto_property *p, *previous = NULL;
113
114
0
  *message_expiry = MSG_EXPIRY_INFINITE;
115
116
0
  if(!proplist){
117
0
    return;
118
0
  }
119
120
0
  p = *proplist;
121
0
  while(p){
122
0
    if(mosquitto_property_identifier(p) == MQTT_PROP_MESSAGE_EXPIRY_INTERVAL){
123
0
      *message_expiry = mosquitto_property_int32_value(p);
124
0
      if(p == *proplist){
125
0
        *proplist = mosquitto_property_next(p);
126
0
      }else{
127
0
        previous->next = mosquitto_property_next(p);
128
0
      }
129
0
      mosquitto_property_free(&p);
130
0
      return;
131
132
0
    }
133
0
    previous = p;
134
0
    p = mosquitto_property_next(p);
135
0
  }
136
0
}
137
138
139
static void queue_plugin_msgs(void)
140
0
{
141
0
  struct mosquitto__message_v5 *msg, *tmp;
142
0
  struct mosquitto *context;
143
0
  uint32_t message_expiry;
144
145
0
  DL_FOREACH_SAFE(db.plugin_msgs, msg, tmp){
146
0
    DL_DELETE(db.plugin_msgs, msg);
147
148
0
    read_message_expiry_interval(&msg->properties, &message_expiry);
149
150
0
    if(msg->clientid){
151
0
      HASH_FIND(hh_id, db.contexts_by_id, msg->clientid, strlen(msg->clientid), context);
152
0
      if(context){
153
0
        single_publish(context, msg, message_expiry);
154
0
      }
155
0
    }else{
156
0
      db__messages_easy_queue(NULL, msg->topic, (uint8_t)msg->qos, (uint32_t)msg->payloadlen, msg->payload, msg->retain, message_expiry, &msg->properties);
157
0
    }
158
0
    mosquitto_FREE(msg->topic);
159
0
    mosquitto_FREE(msg->payload);
160
0
    mosquitto_property_free_all(&msg->properties);
161
0
    mosquitto_FREE(msg->clientid);
162
0
    mosquitto_FREE(msg);
163
0
  }
164
0
}
165
166
167
void loop__update_next_event(time_t new_ms)
168
0
{
169
0
  if(new_ms > 0 && new_ms < db.next_event_ms){
170
0
    db.next_event_ms = new_ms;
171
0
  }
172
0
}
173
174
175
int mosquitto_main_loop(struct mosquitto__listener_sock *listensock, int listensock_count)
176
0
{
177
0
#ifdef WITH_PERSISTENCE
178
0
  time_t last_backup = mosquitto_time();
179
0
#endif
180
0
  int rc;
181
182
183
0
  watchdog__init();
184
#if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_LWS && LWS_LIBRARY_VERSION_NUMBER == 3002000
185
  memset(&sul, 0, sizeof(struct lws_sorted_usec_list));
186
#endif
187
188
0
  db.now_s = mosquitto_time();
189
0
  db.now_real_s = time(NULL);
190
191
0
#ifdef WITH_BRIDGE
192
0
  rc = bridge__register_local_connections();
193
0
  if(rc){
194
0
    return rc;
195
0
  }
196
0
#endif
197
198
0
  while(g_run){
199
0
    retain__expiry_check();
200
0
    queue_plugin_msgs();
201
0
    context__free_disused();
202
203
0
    db.next_event_ms = 86400000;
204
0
#ifdef WITH_SYS_TREE
205
0
    if(db.config->sys_interval > 0){
206
0
      sys_tree__update(false);
207
0
    }
208
0
#endif
209
210
0
    keepalive__check();
211
0
    watchdog__check();
212
213
0
#ifdef WITH_BRIDGE
214
0
    bridge_check();
215
0
#endif
216
0
    plugin__handle_tick();
217
0
    session_expiry__check();
218
0
    will_delay__check();
219
220
0
    rc = mux__handle(listensock, listensock_count);
221
0
    if(rc){
222
0
      return rc;
223
0
    }
224
225
0
#ifdef WITH_PERSISTENCE
226
0
    if(db.config->persistence && db.config->autosave_interval){
227
0
      if(db.config->autosave_on_changes){
228
0
        if(db.persistence_changes >= db.config->autosave_interval){
229
0
          persist__backup(false);
230
0
          db.persistence_changes = 0;
231
0
        }
232
0
      }else{
233
0
        if(last_backup + db.config->autosave_interval < db.now_s){
234
0
          persist__backup(false);
235
0
          last_backup = db.now_s;
236
0
        }
237
0
      }
238
0
    }
239
0
#endif
240
241
0
    rc = signal__flag_check();
242
0
    if(rc){
243
0
      return rc;
244
0
    }
245
246
#if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_LWS
247
    for(int i=0; i<db.config->listener_count; i++){
248
      /* Extremely hacky, should be using the lws provided external poll
249
       * interface, but their interface has changed recently and ours
250
       * will soon, so for now websockets clients are second class
251
       * citizens. */
252
      if(db.config->listeners[i].ws_context){
253
#if LWS_LIBRARY_VERSION_NUMBER > 3002000
254
        lws_service(db.config->listeners[i].ws_context, -1);
255
#elif LWS_LIBRARY_VERSION_NUMBER == 3002000
256
        lws_sul_schedule(db.config->listeners[i].ws_context, 0, &sul, lws__sul_callback, 10);
257
        lws_service(db.config->listeners[i].ws_context, 0);
258
#else
259
        lws_service(db.config->listeners[i].ws_context, 0);
260
#endif
261
262
      }
263
    }
264
#endif
265
0
  }
266
267
0
  return MOSQ_ERR_SUCCESS;
268
0
}
269
270
271
void do_disconnect(struct mosquitto *context, int reason)
272
44
{
273
44
  const char *id;
274
#if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_LWS
275
  bool is_duplicate = false;
276
#endif
277
278
44
  if(context->state == mosq_cs_disconnected){
279
0
    return;
280
0
  }
281
#if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_LWS
282
  if(context->wsi){
283
    if(context->state == mosq_cs_duplicate){
284
      is_duplicate = true;
285
    }
286
287
    if(context->state != mosq_cs_disconnecting && context->state != mosq_cs_disconnect_with_will){
288
      mosquitto__set_state(context, mosq_cs_disconnect_ws);
289
    }
290
    if(context->wsi){
291
      lws_callback_on_writable(context->wsi);
292
    }
293
    if(context->sock != INVALID_SOCKET){
294
      HASH_DELETE(hh_sock, db.contexts_by_sock, context);
295
      mux__delete(context);
296
      context->sock = INVALID_SOCKET;
297
    }
298
    if(is_duplicate){
299
      /* This occurs if another client is taking over the same client id.
300
       * It is important to remove this from the by_id hash here, so it
301
       * doesn't leave us with multiple clients in the hash with the same
302
       * id. Websockets doesn't actually close the connection here,
303
       * unlike for normal clients, which means there is extra time when
304
       * there could be two clients with the same id in the hash. */
305
      context__remove_from_by_id(context);
306
    }
307
  }else
308
#endif
309
44
  {
310
44
    if(db.config->connection_messages == true){
311
0
      if(context->id){
312
0
        id = context->id;
313
0
      }else{
314
0
        id = context->address;
315
0
      }
316
0
      if(context->state != mosq_cs_disconnecting && context->state != mosq_cs_disconnect_with_will){
317
0
        switch(reason){
318
0
          case MOSQ_ERR_SUCCESS:
319
0
            break;
320
0
          case MOSQ_ERR_MALFORMED_PACKET:
321
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to malformed packet.", id);
322
0
            break;
323
0
          case MOSQ_ERR_PROTOCOL:
324
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to protocol error.", id);
325
0
            break;
326
0
          case MOSQ_ERR_CONN_LOST:
327
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s closed its connection.", id);
328
0
            break;
329
0
          case MOSQ_ERR_AUTH:
330
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, not authorised.", id);
331
0
            break;
332
0
          case MOSQ_ERR_KEEPALIVE:
333
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s has exceeded timeout, disconnecting.", id);
334
0
            break;
335
0
          case MOSQ_ERR_OVERSIZE_PACKET:
336
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to oversize packet.", id);
337
0
            break;
338
0
          case MOSQ_ERR_PAYLOAD_SIZE:
339
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to oversize payload.", id);
340
0
            break;
341
0
          case MOSQ_ERR_NOMEM:
342
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to out of memory.", id);
343
0
            break;
344
0
          case MOSQ_ERR_NOT_SUPPORTED:
345
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to using not allowed feature (QoS too high, retain not supported, or bad AUTH method).", id);
346
0
            break;
347
0
          case MOSQ_ERR_ADMINISTRATIVE_ACTION:
348
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s been disconnected by administrative action.", id);
349
0
            break;
350
0
          case MOSQ_ERR_ERRNO:
351
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected: %s.", id, strerror(errno));
352
0
            break;
353
0
          case MOSQ_ERR_RECEIVE_MAXIMUM_EXCEEDED:
354
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to exceeding the receive maximum.", id);
355
0
            break;
356
0
          case MOSQ_ERR_IMPLEMENTATION_SPECIFIC:
357
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, implementation specific error.", id);
358
0
            break;
359
0
          case MOSQ_ERR_CLIENT_IDENTIFIER_NOT_VALID:
360
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, client identifier not valid.", id);
361
0
            break;
362
0
          case MOSQ_ERR_BAD_USERNAME_OR_PASSWORD:
363
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, bad username or password.", id);
364
0
            break;
365
0
          case MOSQ_ERR_SERVER_UNAVAILABLE:
366
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, server unavailable.", id);
367
0
            break;
368
0
          case MOSQ_ERR_SERVER_BUSY:
369
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, server busy.", id);
370
0
            break;
371
0
          case MOSQ_ERR_BANNED:
372
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, client banned.", id);
373
0
            break;
374
0
          case MOSQ_ERR_BAD_AUTHENTICATION_METHOD:
375
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, bad authentication method.", id);
376
0
            break;
377
0
          case MOSQ_ERR_QUOTA_EXCEEDED:
378
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, quota exceeded.", id);
379
0
            break;
380
0
          case MOSQ_ERR_CONNECTION_RATE_EXCEEDED:
381
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, connection rate exceeded.", id);
382
0
            break;
383
0
          case MOSQ_ERR_SESSION_TAKEN_OVER:
384
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, session taken over.", id);
385
0
            break;
386
0
          case MOSQ_ERR_TOPIC_ALIAS_INVALID:
387
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, topic alias invalid.", id);
388
0
            break;
389
0
          case MOSQ_ERR_HTTP_BAD_ORIGIN:
390
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, non-matching http origin.", id);
391
0
            break;
392
0
          case MOSQ_ERR_PROXY:
393
            /* This was a proxy v2 health check connection, so don't report */
394
0
            break;
395
0
          default:
396
0
            log__printf(NULL, MOSQ_LOG_NOTICE, "Bad socket read/write on client %s: %s", id, mosquitto_strerror(reason));
397
0
            break;
398
0
        }
399
0
      }else{
400
0
        if(reason == MOSQ_ERR_ADMINISTRATIVE_ACTION){
401
0
          log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s been disconnected by administrative action.", id);
402
0
        }else{
403
0
          log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected.", id);
404
0
        }
405
0
      }
406
0
    }
407
44
    mux__delete(context);
408
44
    context__disconnect(context, reason);
409
44
  }
410
44
}