• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-mcu / 1755623041

07 Apr 2025 10:56AM UTC coverage: 57.932% (+0.06%) from 57.871%
1755623041

push

gitlab-ci

danielskinstad
fix: reschedule work items with backoff on retry errors

If `MENDER_RETRY_ERROR` is detected in a work function it will be retried
with a separate backoff interval which doubles on each try. It will
continue until it reaches the max backoff interval and keep
retrying using the max backoff interval.

Added configurable options for backoff:
* `MENDER_RETRY_ERROR_BACKOFF`
* `MENDER_RETRY_ERROR_MAX_BACKOFF`

Ticket: MEN-8222
Changelog: Title

Signed-off-by: Daniel Skinstad Drabitzius <daniel.drabitzius@northern.tech>

35 of 44 new or added lines in 6 files covered. (79.55%)

346 existing lines in 4 files now uncovered.

2308 of 3984 relevant lines covered (57.93%)

69.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.4
/src/core/api.c
1
/**
2
 * @file      api.c
3
 * @brief     Implementation of the Mender API
4
 *
5
 * Copyright joelguittet and mender-mcu-client contributors
6
 * Copyright Northern.tech AS
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20

21
#include "alloc.h"
22
#include "api.h"
23
#include "artifact.h"
24
#include "error-counters.h"
25
#include "os.h"
26
#include "storage.h"
27
#include "http.h"
28
#include "log.h"
29
#include "tls.h"
30
#include "utils.h"
31

32
/**
33
 * @brief Paths of the mender-server APIs
34
 */
35
#define MENDER_API_PATH_POST_AUTHENTICATION_REQUESTS "/api/devices/v1/authentication/auth_requests"
36
#define MENDER_API_PATH_GET_NEXT_DEPLOYMENT          "/api/devices/v1/deployments/device/deployments/next"
37
#define MENDER_API_PATH_POST_NEXT_DEPLOYMENT_V2      "/api/devices/v2/deployments/device/deployments/next"
38
#define MENDER_API_PATH_PUT_DEPLOYMENT_STATUS        "/api/devices/v1/deployments/device/deployments/%s/status"
39
#define MENDER_API_PATH_PUT_DEPLOYMENT_LOGS          "/api/devices/v1/deployments/device/deployments/%s/log"
40
#define MENDER_API_PATH_GET_DEVICE_CONFIGURATION     "/api/devices/v1/deviceconfig/configuration"
41
#define MENDER_API_PATH_PUT_DEVICE_CONFIGURATION     "/api/devices/v1/deviceconfig/configuration"
42
#define MENDER_API_PATH_GET_DEVICE_CONNECT           "/api/devices/v1/deviceconnect/connect"
43
#define MENDER_API_PATH_PUT_DEVICE_ATTRIBUTES        "/api/devices/v1/inventory/device/attributes"
44

45
/**
46
 * @brief Mender API configuration
47
 */
48
static mender_api_config_t api_config;
49

50
/**
51
 * @brief Authentication token
52
 */
53
static char *api_jwt = NULL;
54

55
/**
56
 * @brief A mutex ensuring there are no concurrent operations using or updating the authentication token
57
 */
58
static void *auth_lock = NULL;
59

60
/**
61
 * @brief HTTP callback used to handle text content
62
 * @param event HTTP client event
63
 * @param data Data received
64
 * @param data_length Data length
65
 * @param params Callback parameters
66
 * @return MENDER_OK if the function succeeds, error code otherwise
67
 */
68
static mender_err_t mender_api_http_text_callback(mender_http_client_event_t event, void *data, size_t data_length, void *params);
69

70
/**
71
 * @brief Perform authentication of the device, retrieve token from mender-server used for the next requests
72
 * @return MENDER_OK if the function succeeds, error code otherwise
73
 */
74
static mender_err_t perform_authentication(void);
75

76
/**
77
 * @brief Ensure authenticated and holding the #auth_lock
78
 * @return MENDER_OK if success, MENDER_LOCK_FAILED in case of lock failure, other errors otherwise
79
 */
80
static mender_err_t ensure_authenticated_and_locked(void);
81

82
mender_err_t
83
mender_api_init(mender_api_config_t *config) {
29✔
84
    assert(NULL != config);
29✔
85
    assert(NULL != config->device_type);
29✔
86
    assert(NULL != config->host);
29✔
87
    assert(NULL != config->identity_cb);
29✔
88

89
    mender_err_t ret;
90

91
    /* Save configuration */
92
    memcpy(&api_config, config, sizeof(mender_api_config_t));
29✔
93

94
    /* Initializations */
95
    mender_http_config_t mender_http_config = { .host = api_config.host };
29✔
96
    if (MENDER_OK != (ret = mender_http_init(&mender_http_config))) {
29✔
97
        mender_log_error("Unable to initialize HTTP");
×
98
        return ret;
×
99
    }
100

101
    if (MENDER_OK != (ret = mender_os_mutex_create(&auth_lock))) {
29✔
102
        mender_log_error("Unable to initialize authentication lock");
×
103
        return ret;
×
104
    }
105

106
    return ret;
29✔
107
}
108

109
mender_err_t
110
mender_api_drop_authentication_data(void) {
6✔
111
    mender_err_t ret;
112
    if (MENDER_OK != (ret = mender_os_mutex_take(auth_lock, -1))) {
6✔
113
        mender_log_error("Unable to obtain the authentication lock");
×
114
        return MENDER_LOCK_FAILED;
×
115
    }
116
    FREE_AND_NULL(api_jwt);
6✔
117
    if (MENDER_OK != (ret = mender_os_mutex_give(auth_lock))) {
6✔
118
        mender_log_error("Unable to release the authentication lock");
×
119
    }
120

121
    return ret;
6✔
122
}
123

124
mender_err_t
125
mender_api_ensure_authenticated(void) {
6✔
126
    mender_err_t ret = ensure_authenticated_and_locked();
6✔
127
    if (MENDER_LOCK_FAILED == ret) {
5✔
128
        /* Error already logged. */
129
        return MENDER_FAIL;
×
130
    }
131
    bool authenticated = ((MENDER_OK == ret) || (MENDER_DONE == ret));
5✔
132

133
    if (MENDER_OK != (ret = mender_os_mutex_give(auth_lock))) {
5✔
134
        mender_log_error("Unable to release the authentication lock");
×
135
    }
136

137
    return authenticated ? ret : MENDER_FAIL;
5✔
138
}
139

140
static mender_err_t
141
ensure_authenticated_and_locked(void) {
119✔
142
    mender_err_t ret;
143

144
    if (MENDER_OK != (ret = mender_os_mutex_take(auth_lock, -1))) {
119✔
145
        mender_log_error("Unable to obtain the authentication lock");
×
146
        return MENDER_LOCK_FAILED;
×
147
    }
148

149
    if (NULL != api_jwt) {
119✔
150
        return MENDER_DONE;
87✔
151
    }
152

153
    /* Perform authentication with the mender server */
154
    if (MENDER_OK != (ret = perform_authentication())) {
32✔
155
        mender_log_error("Authentication failed");
4✔
156
        return ret;
4✔
157
    } else {
158
        mender_log_debug("Authenticated successfully");
27✔
159
    }
160

161
    return ret;
27✔
162
}
163

164
static mender_err_t
165
perform_authentication(void) {
32✔
166
    mender_err_t             ret;
167
    char                    *public_key_pem   = NULL;
32✔
168
    const mender_identity_t *identity         = NULL;
32✔
169
    cJSON                   *json_identity    = NULL;
32✔
170
    char                    *identity_info    = NULL;
32✔
171
    cJSON                   *json_payload     = NULL;
32✔
172
    char                    *payload          = NULL;
32✔
173
    char                    *response         = NULL;
32✔
174
    char                    *signature        = NULL;
32✔
175
    size_t                   signature_length = 0;
32✔
176
    int                      status           = 0;
32✔
177

178
    /* Get public key in PEM format */
179
    if (MENDER_OK != (ret = mender_tls_get_public_key_pem(&public_key_pem))) {
32✔
180
        mender_log_error("Unable to get public key");
×
181
        goto END;
×
182
    }
183

184
    /* Get identity (we don't own the returned data) */
185
    if (MENDER_OK != (ret = api_config.identity_cb(&identity))) {
32✔
186
        mender_log_error("Unable to get identity");
×
187
        goto END;
×
188
    }
189

190
    /* Format identity */
191
    if (MENDER_OK != (ret = mender_utils_identity_to_json(identity, &json_identity))) {
32✔
192
        mender_log_error("Unable to format identity");
×
193
        goto END;
×
194
    }
195
    if (NULL == (identity_info = cJSON_PrintUnformatted(json_identity))) {
32✔
196
        mender_log_error("Unable to allocate memory");
×
197
        ret = MENDER_FAIL;
×
198
        goto END;
×
199
    }
200

201
    /* Format payload */
202
    if (NULL == (json_payload = cJSON_CreateObject())) {
32✔
203
        mender_log_error("Unable to allocate memory");
×
204
        ret = MENDER_FAIL;
×
205
        goto END;
×
206
    }
207
    cJSON_AddStringToObject(json_payload, "id_data", identity_info);
32✔
208
    cJSON_AddStringToObject(json_payload, "pubkey", public_key_pem);
32✔
209
    if (NULL != api_config.tenant_token) {
32✔
210
        cJSON_AddStringToObject(json_payload, "tenant_token", api_config.tenant_token);
32✔
211
    }
212
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
32✔
213
        mender_log_error("Unable to allocate memory");
×
214
        ret = MENDER_FAIL;
×
215
        goto END;
×
216
    }
217

218
    /* Sign payload */
219
    if (MENDER_OK != (ret = mender_tls_sign_payload(payload, &signature, &signature_length))) {
32✔
220
        mender_log_error("Unable to sign payload");
×
221
        goto END;
×
222
    }
223

224
    /* Perform HTTP request */
225
    if (MENDER_OK
31✔
226
        != (ret = mender_http_perform(NULL,
32✔
227
                                      MENDER_API_PATH_POST_AUTHENTICATION_REQUESTS,
228
                                      MENDER_HTTP_POST,
229
                                      payload,
230
                                      signature,
231
                                      &mender_api_http_text_callback,
232
                                      (void *)&response,
233
                                      &status))) {
234
        mender_log_error("Unable to perform HTTP request");
4✔
235
        mender_err_count_net_inc();
4✔
236
        goto END;
4✔
237
    }
238

239
    /* Treatment depending of the status */
240
    if (200 == status) {
27✔
241
        if (NULL == response) {
27✔
242
            mender_log_error("Response is empty");
×
243
            ret = MENDER_FAIL;
×
244
            goto END;
×
245
        }
246
        if (NULL != api_jwt) {
27✔
247
            mender_free(api_jwt);
×
248
        }
249
        if (NULL == (api_jwt = mender_utils_strdup(response))) {
27✔
250
            mender_log_error("Unable to allocate memory");
×
251
            ret = MENDER_FAIL;
×
252
            goto END;
×
253
        }
254
        ret = MENDER_OK;
27✔
255
    } else {
256
        mender_api_print_response_error(response, status);
×
257
        /* Maybe the identity is wrong? Let's make sure we get fresh data for the next attempt. */
258
        FREE_AND_NULL(identity_info);
×
NEW
259
        ret = MENDER_RETRY_ERROR;
×
260
    }
261

262
END:
31✔
263

264
    /* Release memory */
265
    mender_free(response);
31✔
266
    mender_free(signature);
31✔
267
    mender_free(payload);
31✔
268
    cJSON_Delete(json_payload);
31✔
269
    cJSON_Delete(json_identity);
31✔
270
    mender_free(identity_info);
31✔
271
    mender_free(public_key_pem);
31✔
272

273
    return ret;
31✔
274
}
275

276
/**
277
 * @see mender_http_perform()
278
 */
279
static mender_err_t
280
authenticated_http_perform(char *path, mender_http_method_t method, char *payload, char *signature, char **response, int *status) {
113✔
281
    mender_err_t ret;
282

283
    if (MENDER_IS_ERROR(ret = ensure_authenticated_and_locked())) {
113✔
284
        /* Errors already logged. */
285
        if (MENDER_LOCK_FAILED != ret) {
4✔
286
            if (MENDER_OK != mender_os_mutex_give(auth_lock)) {
4✔
287
                mender_log_error("Unable to release the authentication lock");
×
288
                return MENDER_FAIL;
×
289
            }
290
        }
291
        return ret;
4✔
292
    }
293

294
    ret = mender_http_perform(api_jwt, path, method, payload, signature, &mender_api_http_text_callback, response, status);
109✔
295
    if (MENDER_OK != mender_os_mutex_give(auth_lock)) {
95✔
296
        mender_log_error("Unable to release the authentication lock");
×
297
        return MENDER_FAIL;
×
298
    }
299
    if (MENDER_OK != ret) {
95✔
300
        /* HTTP errors already logged. */
301
        mender_err_count_net_inc();
1✔
302
        return ret;
1✔
303
    }
304

305
    if (401 == *status) {
94✔
306
        /* Unauthorized => try to re-authenticate and perform the request again */
307
        mender_log_info("Trying to re-authenticate");
×
308
        FREE_AND_NULL(api_jwt);
×
309
        if (MENDER_IS_ERROR(ret = ensure_authenticated_and_locked())) {
×
310
            mender_free(*response);
×
311
            ret = mender_http_perform(api_jwt, path, method, payload, signature, &mender_api_http_text_callback, response, status);
×
312
            if (MENDER_OK != mender_os_mutex_give(auth_lock)) {
×
313
                mender_log_error("Unable to release the authentication lock");
×
314
                return MENDER_FAIL;
×
315
            }
316
            if (MENDER_OK != ret) {
×
317
                /* HTTP errors already logged. */
318
                mender_err_count_net_inc();
×
319
            }
320
        } else if (MENDER_LOCK_FAILED != ret) {
×
321
            if (MENDER_OK != mender_os_mutex_give(auth_lock)) {
×
322
                mender_log_error("Unable to release the authentication lock");
×
323
                return MENDER_FAIL;
×
324
            }
325
        }
326
    }
327

328
    return ret;
94✔
329
}
330

331
static mender_err_t
332
api_check_for_deployment_v2(int *status, char **response) {
39✔
333
    assert(NULL != status);
39✔
334
    assert(NULL != response);
39✔
335

336
    mender_err_t ret           = MENDER_FAIL;
39✔
337
    cJSON       *json_payload  = NULL;
39✔
338
    char        *payload       = NULL;
39✔
339
    const char  *artifact_name = NULL;
39✔
340
#ifdef CONFIG_MENDER_PROVIDES_DEPENDS
341
#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT
342
    mender_key_value_list_t *provides = NULL;
39✔
343
#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */
344
#endif /* CONFIG_MENDER_PROVIDES_DEPENDS */
345

346
    /* Create payload */
347
    if (NULL == (json_payload = cJSON_CreateObject())) {
39✔
348
        mender_log_error("Unable to allocate memory");
×
349
        goto END;
×
350
    }
351

352
    /* Add "device_provides" entity to payload */
353
    cJSON *json_provides = NULL;
39✔
354
    if (NULL == (json_provides = cJSON_AddObjectToObject(json_payload, "device_provides"))) {
39✔
355
        mender_log_error("Unable to allocate memory");
×
356
        goto END;
×
357
    }
358

359
    if (NULL == cJSON_AddStringToObject(json_provides, "device_type", api_config.device_type)) {
39✔
360
        mender_log_error("Unable to allocate memory");
×
361
        goto END;
×
362
    }
363

364
#ifdef CONFIG_MENDER_PROVIDES_DEPENDS
365
#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT
366
    /* Add provides from storage */
367
    if (MENDER_FAIL == mender_storage_get_provides(&provides)) {
39✔
368
        mender_log_error("Unable to get provides");
×
369
        goto END;
×
370
    }
371
    for (mender_key_value_list_t *item = provides; NULL != item; item = item->next) {
44✔
372
        if (NULL == cJSON_AddStringToObject(json_provides, item->key, item->value)) {
5✔
373
            mender_log_error("Unable to allocate memory");
×
374
            goto END;
×
375
        }
376
    }
377
#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */
378
#endif /* CONFIG_MENDER_PROVIDES_DEPENDS */
379

380
    if ((MENDER_OK != mender_storage_get_artifact_name(&artifact_name)) && (NULL != artifact_name)) {
39✔
381
        mender_log_error("Unable to get artifact name");
×
382
        return MENDER_FAIL;
×
383
    }
384

385
    if (NULL == cJSON_AddStringToObject(json_provides, "artifact_name", artifact_name)) {
39✔
386
        mender_log_error("Unable to allocate memory");
×
387
        goto END;
×
388
    }
389

390
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
39✔
391
        mender_log_error("Unable to allocate memory");
×
392
        goto END;
×
393
    }
394

395
    /* Perform HTTP request */
396
    if (MENDER_OK != (ret = authenticated_http_perform(MENDER_API_PATH_POST_NEXT_DEPLOYMENT_V2, MENDER_HTTP_POST, payload, NULL, response, status))) {
39✔
397
        mender_log_error("Unable to perform HTTP request");
×
398
        goto END;
×
399
    }
400

401
    ret = MENDER_OK;
27✔
402

403
END:
27✔
404

405
#ifdef CONFIG_MENDER_PROVIDES_DEPENDS
406
#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT
407
    mender_utils_key_value_list_free(provides);
27✔
408
#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */
409
#endif /* CONFIG_MENDER_PROVIDES_DEPENDS */
410
    cJSON_Delete(json_payload);
27✔
411
    mender_free(payload);
27✔
412
    return ret;
27✔
413
}
414

415
static mender_err_t
416
api_check_for_deployment_v1(int *status, char **response) {
×
417

418
    assert(NULL != status);
×
419
    assert(NULL != response);
×
420

421
    mender_err_t ret           = MENDER_FAIL;
×
422
    char        *path          = NULL;
×
423
    const char  *artifact_name = NULL;
×
424

425
    if ((MENDER_OK != mender_storage_get_artifact_name(&artifact_name)) && (NULL != artifact_name)) {
×
426
        mender_log_error("Unable to get artifact name");
×
427
        return MENDER_FAIL;
×
428
    }
429

430
    /* Compute path */
431
    if (-1 == mender_utils_asprintf(&path, MENDER_API_PATH_GET_NEXT_DEPLOYMENT "?artifact_name=%s&device_type=%s", artifact_name, api_config.device_type)) {
×
432
        mender_log_error("Unable to allocate memory");
×
433
        goto END;
×
434
    }
435

436
    /* Perform HTTP request */
437
    if (MENDER_OK != (ret = authenticated_http_perform(path, MENDER_HTTP_GET, NULL, NULL, response, status))) {
×
438
        mender_log_error("Unable to perform HTTP request");
×
439
        goto END;
×
440
    }
441

442
    ret = MENDER_OK;
×
443

444
END:
×
445

446
    /* Release memory */
447
    mender_free(path);
×
448

449
    return ret;
×
450
}
451

452
mender_err_t
453
mender_api_check_for_deployment(mender_api_deployment_data_t *deployment) {
39✔
454

455
    assert(NULL != deployment);
39✔
456
    mender_err_t ret      = MENDER_FAIL;
39✔
457
    char        *response = NULL;
39✔
458
    int          status   = 0;
39✔
459

460
    if (MENDER_FAIL == (ret = api_check_for_deployment_v2(&status, &response))) {
39✔
461
        goto END;
×
462
    }
463

464
    /* Yes, 404 still means MENDER_OK above */
465
    if (404 == status) {
27✔
466
        mender_log_debug("POST request to v2 version of the deployments API failed, falling back to v1 version and GET");
467
        FREE_AND_NULL(response);
×
468
        if (MENDER_FAIL == (ret = api_check_for_deployment_v1(&status, &response))) {
×
469
            goto END;
×
470
        }
471
    }
472

473
    /* Treatment depending of the status */
474
    if (200 == status) {
27✔
475
        cJSON *json_response = cJSON_Parse(response);
12✔
476
        if (NULL != json_response) {
12✔
477
            cJSON *json_id = cJSON_GetObjectItem(json_response, "id");
12✔
478
            if (NULL != json_id) {
12✔
479
                if (NULL == (deployment->id = mender_utils_strdup(cJSON_GetStringValue(json_id)))) {
12✔
480
                    ret = MENDER_FAIL;
×
481
                    goto END;
×
482
                }
483
            }
484
            cJSON *json_artifact = cJSON_GetObjectItem(json_response, "artifact");
12✔
485
            if (NULL != json_artifact) {
12✔
486
                cJSON *json_artifact_name = cJSON_GetObjectItem(json_artifact, "artifact_name");
12✔
487
                if (NULL != json_artifact_name) {
12✔
488
                    if (NULL == (deployment->artifact_name = mender_utils_strdup(cJSON_GetStringValue(json_artifact_name)))) {
12✔
489
                        ret = MENDER_FAIL;
×
490
                        goto END;
×
491
                    }
492
                }
493
                cJSON *json_source = cJSON_GetObjectItem(json_artifact, "source");
12✔
494
                if (NULL != json_source) {
12✔
495
                    cJSON *json_uri = cJSON_GetObjectItem(json_source, "uri");
12✔
496
                    if (NULL != json_uri) {
12✔
497
                        if (NULL == (deployment->uri = mender_utils_strdup(cJSON_GetStringValue(json_uri)))) {
12✔
498
                            ret = MENDER_FAIL;
×
499
                            goto END;
×
500
                        }
501
                        ret = MENDER_OK;
12✔
502
                    } else {
503
                        mender_log_error("Invalid response");
×
504
                        ret = MENDER_FAIL;
×
505
                    }
506
                } else {
507
                    mender_log_error("Invalid response");
×
508
                    ret = MENDER_FAIL;
×
509
                }
510
                cJSON *json_device_types_compatible = cJSON_GetObjectItem(json_artifact, "device_types_compatible");
12✔
511
                if (NULL != json_device_types_compatible && cJSON_IsArray(json_device_types_compatible)) {
12✔
512
                    deployment->device_types_compatible_size = cJSON_GetArraySize(json_device_types_compatible);
12✔
513
                    deployment->device_types_compatible      = (char **)mender_malloc(deployment->device_types_compatible_size * sizeof(char *));
12✔
514
                    if (NULL == deployment->device_types_compatible) {
12✔
515
                        mender_log_error("Unable to allocate memory");
×
516
                        ret = MENDER_FAIL;
×
517
                        goto END;
×
518
                    }
519
                    for (size_t i = 0; i < deployment->device_types_compatible_size; i++) {
24✔
520
                        cJSON *json_device_type = cJSON_GetArrayItem(json_device_types_compatible, i);
12✔
521
                        if (NULL != json_device_type && cJSON_IsString(json_device_type)) {
12✔
522
                            if (NULL == (deployment->device_types_compatible[i] = mender_utils_strdup(cJSON_GetStringValue(json_device_type)))) {
12✔
523
                                ret = MENDER_FAIL;
×
524
                                goto END;
×
525
                            }
526
                        } else {
527
                            mender_log_error("Could not get device type form device_types_compatible array");
×
528
                            ret = MENDER_FAIL;
×
529
                        }
530
                    }
531
                } else {
532
                    mender_log_error("Could not load device_types_compatible");
×
533
                    ret = MENDER_FAIL;
×
534
                }
535
            } else {
536
                mender_log_error("Invalid response");
×
537
                ret = MENDER_FAIL;
×
538
            }
539
            cJSON_Delete(json_response);
12✔
540
        } else {
541
            mender_log_error("Invalid response");
×
542
            ret = MENDER_FAIL;
×
543
        }
544
    } else if (204 == status) {
15✔
545
        /* No response expected */
546
        ret = MENDER_NOT_FOUND;
15✔
547
    } else {
548
        mender_api_print_response_error(response, status);
×
NEW
549
        ret = MENDER_RETRY_ERROR;
×
550
    }
551

552
END:
27✔
553

554
    /* Release memory */
555
    mender_free(response);
27✔
556

557
    return ret;
27✔
558
}
559

560
#ifdef CONFIG_MENDER_DEPLOYMENT_LOGS
561
static mender_err_t mender_api_publish_deployment_logs(const char *id);
562
#endif /* CONFIG_MENDER_DEPLOYMENT_LOGS */
563

564
mender_err_t
565
mender_api_publish_deployment_status(const char *id, mender_deployment_status_t deployment_status) {
42✔
566
    assert(NULL != id);
42✔
567

568
    mender_err_t ret;
569
    const char  *value        = NULL;
42✔
570
    cJSON       *json_payload = NULL;
42✔
571
    char        *payload      = NULL;
42✔
572
    char        *path         = NULL;
42✔
573
    char        *response     = NULL;
42✔
574
    int          status       = 0;
42✔
575

576
    /* Deployment status to string */
577
    if (NULL == (value = mender_utils_deployment_status_to_string(deployment_status))) {
42✔
578
        mender_log_error("Invalid status");
×
579
        ret = MENDER_FAIL;
×
580
        goto END;
×
581
    }
582

583
    /* Format payload */
584
    if (NULL == (json_payload = cJSON_CreateObject())) {
42✔
585
        mender_log_error("Unable to allocate memory");
×
586
        ret = MENDER_FAIL;
×
587
        goto END;
×
588
    }
589
    cJSON_AddStringToObject(json_payload, "status", value);
42✔
590
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
42✔
591
        mender_log_error("Unable to allocate memory");
×
592
        ret = MENDER_FAIL;
×
593
        goto END;
×
594
    }
595

596
    /* Compute path */
597
    size_t str_length = strlen(MENDER_API_PATH_PUT_DEPLOYMENT_STATUS) - strlen("%s") + strlen(id) + 1;
42✔
598
    if (NULL == (path = (char *)mender_malloc(str_length))) {
42✔
599
        mender_log_error("Unable to allocate memory");
×
600
        ret = MENDER_FAIL;
×
601
        goto END;
×
602
    }
603
    snprintf(path, str_length, MENDER_API_PATH_PUT_DEPLOYMENT_STATUS, id);
42✔
604

605
    /* Perform HTTP request */
606
    if (MENDER_OK != (ret = authenticated_http_perform(path, MENDER_HTTP_PUT, payload, NULL, &response, &status))) {
42✔
607
        mender_log_error("Unable to perform HTTP request");
3✔
608
        goto END;
3✔
609
    }
610

611
    /* Treatment depending of the status */
612
    if (204 == status) {
37✔
613
        /* No response expected */
614
        ret = MENDER_OK;
36✔
615
    } else if (409 == status) {
1✔
616
        /* Deployment aborted */
617
        mender_api_print_response_error(response, status);
1✔
618
        ret = MENDER_ABORTED;
1✔
619
    } else {
620
        mender_api_print_response_error(response, status);
×
NEW
621
        ret = MENDER_RETRY_ERROR;
×
622
    }
623

624
END:
40✔
625

626
    /* Release memory */
627
    mender_free(response);
40✔
628
    mender_free(path);
40✔
629
    mender_free(payload);
40✔
630
    cJSON_Delete(json_payload);
40✔
631

632
#ifdef CONFIG_MENDER_DEPLOYMENT_LOGS
633
    /* Do this after we have released memory above, potentially giving us some
634
       extra room we may need. */
635
    if ((MENDER_OK == ret) && (MENDER_DEPLOYMENT_STATUS_FAILURE == deployment_status)) {
40✔
636
        /* Successfully reported a deployment failure, upload deployment
637
           logs.  */
638
        if (MENDER_OK != mender_api_publish_deployment_logs(id)) {
6✔
639
            mender_log_error("Failed to publish deployment logs");
640
        }
641
    }
642
#endif /* CONFIG_MENDER_DEPLOYMENT_LOGS */
643

644
    return ret;
40✔
645
}
646

647
#ifdef CONFIG_MENDER_DEPLOYMENT_LOGS
648
static void
649
append_depl_log_msg(char *msg, void *ctx) {
8✔
650
    assert(NULL != ctx);
8✔
651

652
    char  *tstamp   = NULL;
8✔
653
    char  *level    = NULL;
8✔
654
    char  *log_msg  = NULL;
8✔
655
    cJSON *json_msg = NULL;
8✔
656
    cJSON *messages = ctx;
8✔
657

658
    /* Example log message we expect:
659
     *   "[00:39:06.746,000] <err> mender: Unable to perform HTTP request"
660
     * The code below goes through the string, searches for the expected parts and breaks the string
661
     * down accordingly.
662
     */
663

664
    /* Start by setting log_msg to the whole message. In case all of the
665
       break-down below fails, we send the whole message as the log message with
666
       no extra metadata. */
667
    log_msg = msg;
8✔
668

669
    char *c = msg;
8✔
670
    if ('[' == *c) {
8✔
671
        /* if it does start with a timestamp, like above, store the pointer and find its end */
672
        c++;
8✔
673
        tstamp = c;
8✔
674
        while (('\0' != *c) && (']' != *c)) {
224✔
675
            c++;
216✔
676
        }
677
        if ('\0' == *c) {
8✔
678
            goto DONE_PARSING;
679
        }
680
        *c = '\0';
8✔
681
        c++;
8✔
682
    }
683

684
    if (' ' == *c) {
8✔
685
        /* skip the space */
686
        c++;
8✔
687
    }
688

689
    if ('<' == *c) {
8✔
690
        /* if the log level follow, like above, store the pointer and find its end */
691
        c++;
8✔
692
        level = c;
8✔
693
        while (('\0' != *c) && ('>' != *c)) {
32✔
694
            c++;
24✔
695
        }
696
        if ('\0' == *c) {
8✔
697
            goto DONE_PARSING;
698
        }
699
        *c = '\0';
8✔
700
        c++;
8✔
701
    }
702

703
    if (' ' == *c) {
8✔
704
        /* skip the space */
705
        c++;
8✔
706
    }
707

708
    if ('\0' != *c) {
8✔
709
        log_msg = c;
8✔
710
        if (mender_utils_strbeginswith(log_msg, "mender: ")) {
8✔
711
            log_msg += strlen("mender: ");
8✔
712
        }
713
    }
714

715
DONE_PARSING:
716
    if (NULL == (json_msg = cJSON_CreateObject())) {
8✔
717
        mender_log_error("Unable to allocate memory");
718
        return;
719
    }
720

721
    if (NULL != tstamp) {
8✔
722
        if (NULL == cJSON_AddStringToObject(json_msg, "timestamp", tstamp)) {
8✔
723
            mender_log_error("Unable to allocate memory");
724
            goto END;
725
        }
726
    } else {
727
        if (NULL == cJSON_AddNullToObject(json_msg, "timestamp")) {
728
            mender_log_error("Unable to allocate memory");
729
            goto END;
730
        }
731
    }
732

733
    if (NULL != level) {
8✔
734
        if (NULL == cJSON_AddStringToObject(json_msg, "level", level)) {
8✔
735
            mender_log_error("Unable to allocate memory");
736
            goto END;
737
        }
738
    } else {
739
        if (NULL == cJSON_AddNullToObject(json_msg, "level")) {
740
            mender_log_error("Unable to allocate memory");
741
            goto END;
742
        }
743
    }
744

745
    if (NULL == cJSON_AddStringToObject(json_msg, "message", log_msg)) {
8✔
746
        mender_log_error("Unable to allocate memory");
747
        goto END;
748
    }
749

750
    if (!cJSON_AddItemToArray(messages, json_msg)) {
8✔
751
        mender_log_error("Unable to allocate memory");
752
    }
753
    json_msg = NULL;
8✔
754

755
END:
8✔
756
    cJSON_Delete(json_msg);
8✔
757
}
758

759
static mender_err_t
760
mender_api_publish_deployment_logs(const char *id) {
6✔
761
    assert(NULL != id);
6✔
762

763
    mender_err_t ret;
764
    cJSON       *json_payload  = NULL;
6✔
765
    cJSON       *json_messages = NULL;
6✔
766
    char        *payload       = NULL;
6✔
767
    char        *path          = NULL;
6✔
768
    char        *response      = NULL;
6✔
769
    int          status        = 0;
6✔
770

771
    /* Format payload */
772
    if (NULL == (json_payload = cJSON_CreateObject())) {
6✔
773
        mender_log_error("Unable to allocate memory");
774
        ret = MENDER_FAIL;
775
        goto END;
776
    }
777
    if (NULL == (json_messages = cJSON_AddArrayToObject(json_payload, "messages"))) {
6✔
778
        mender_log_error("Unable to allocate memory");
779
        ret = MENDER_FAIL;
780
        goto END;
781
    }
782

783
    if (MENDER_OK != (ret = mender_storage_deployment_log_walk(append_depl_log_msg, json_messages))) {
6✔
784
        mender_log_error("Failed to add deployment log messages to payload");
785
        ret = MENDER_FAIL;
786
        goto END;
787
    }
788

789
    if (0 == cJSON_GetArraySize(json_messages)) {
6✔
790
        /* Nothing to do, no logs to submit. */
791
        ret = MENDER_OK;
3✔
792
        goto END;
3✔
793
    }
794

795
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
3✔
796
        mender_log_error("Unable to allocate memory");
797
        ret = MENDER_FAIL;
798
        goto END;
799
    }
800
    /* We no longer need the JSON now that we have the string representation so
801
       reclaim that (potentially big) chunk of memory). */
802
    DESTROY_AND_NULL(cJSON_Delete, json_payload);
3✔
803

804
    /* Perform HTTP request */
805
    if (mender_utils_asprintf(&path, MENDER_API_PATH_PUT_DEPLOYMENT_LOGS, id) <= 0) {
3✔
806
        mender_log_error("Unable to allocate memory");
807
        goto END;
808
    }
809

810
    mender_log_info("Publishing deployment logs");
3✔
811
    if (MENDER_OK != (ret = authenticated_http_perform(path, MENDER_HTTP_PUT, payload, NULL, &response, &status))) {
3✔
812
        mender_log_error("Unable to perform HTTP request");
813
        goto END;
814
    }
815

816
    /* Treatment depending of the status */
817
    if (204 == status) {
3✔
818
        /* No response expected */
819
        ret = MENDER_OK;
3✔
820
    } else if (409 == status) {
821
        /* Deployment aborted */
822
        mender_api_print_response_error(response, status);
823
        ret = MENDER_ABORTED;
824
    } else {
825
        mender_api_print_response_error(response, status);
826
        ret = MENDER_FAIL;
827
    }
828

829
END:
6✔
830

831
    /* Release memory */
832
    mender_free(response);
6✔
833
    mender_free(path);
6✔
834
    mender_free(payload);
6✔
835
    cJSON_Delete(json_payload);
6✔
836

837
    return ret;
6✔
838
}
839
#endif /* CONFIG_MENDER_DEPLOYMENT_LOGS */
840

841
#ifndef CONFIG_MENDER_CLIENT_INVENTORY_DISABLE
842

843
mender_err_t
844
mender_api_publish_inventory_data(cJSON *inventory, bool patch) {
29✔
845

846
    mender_err_t ret;
847
    char        *payload  = NULL;
29✔
848
    char        *response = NULL;
29✔
849
    int          status   = 0;
29✔
850

851
    /* Format payload */
852
    if (NULL == (payload = cJSON_PrintUnformatted(inventory))) {
29✔
853
        mender_log_error("Unable to allocate memory");
×
854
        ret = MENDER_FAIL;
×
855
        goto END;
×
856
    }
857

858
    /* Perform HTTP request */
859
    if (MENDER_OK
29✔
860
        != (ret = authenticated_http_perform(
29✔
861
                MENDER_API_PATH_PUT_DEVICE_ATTRIBUTES, patch ? MENDER_HTTP_PATCH : MENDER_HTTP_PUT, payload, NULL, &response, &status))) {
862
        mender_log_error("Unable to perform HTTP request");
2✔
863
        goto END;
2✔
864
    }
865

866
    /* Treatment depending of the status */
867
    if (200 == status) {
27✔
868
        /* No response expected */
869
        ret = MENDER_OK;
27✔
870
    } else {
871
        mender_api_print_response_error(response, status);
×
NEW
872
        ret = MENDER_RETRY_ERROR;
×
873
    }
874

875
END:
29✔
876

877
    /* Release memory */
878
    mender_free(response);
29✔
879
    mender_free(payload);
29✔
880
    cJSON_Delete(inventory);
29✔
881

882
    return ret;
29✔
883
}
884

885
#endif /* CONFIG_MENDER_CLIENT_INVENTORY_DISABLE */
886

887
mender_err_t
888
mender_api_exit(void) {
×
889

890
    /* Release all modules */
891
    mender_http_exit();
×
892

893
    /* Destroy the authentication lock */
894
    mender_os_mutex_delete(auth_lock);
×
895

896
    /* Release memory */
897
    FREE_AND_NULL(api_jwt);
×
898

899
    return MENDER_OK;
×
900
}
901

902
static mender_err_t
903
mender_api_http_text_callback(mender_http_client_event_t event, void *data, size_t data_length, void *params) {
333✔
904

905
    assert(NULL != params);
333✔
906
    char       **response = (char **)params;
333✔
907
    mender_err_t ret      = MENDER_OK;
333✔
908
    char        *tmp;
909

910
    /* Treatment depending of the event */
911
    switch (event) {
333✔
912
        case MENDER_HTTP_EVENT_CONNECTED:
121✔
913
            /* Nothing to do */
914
            break;
121✔
915
        case MENDER_HTTP_EVENT_DATA_RECEIVED:
91✔
916
            /* Check input data */
917
            if ((NULL == data) || (0 == data_length)) {
91✔
918
                mender_log_error("Invalid data received");
×
919
                ret = MENDER_FAIL;
×
920
                break;
×
921
            }
922
            /* Concatenate data to the response */
923
            size_t response_length = (NULL != *response) ? strlen(*response) : 0;
91✔
924
            if (NULL == (tmp = mender_realloc(*response, response_length + data_length + 1))) {
91✔
925
                mender_log_error("Unable to allocate memory");
×
926
                ret = MENDER_FAIL;
×
927
                break;
×
928
            }
929
            *response = tmp;
91✔
930
            memcpy((*response) + response_length, data, data_length);
91✔
931
            *((*response) + response_length + data_length) = '\0';
91✔
932
            break;
91✔
933
        case MENDER_HTTP_EVENT_DISCONNECTED:
121✔
934
            /* Nothing to do */
935
            break;
121✔
936
        case MENDER_HTTP_EVENT_ERROR:
×
937
            /* Downloading the response fails */
938
            mender_log_error("An error occurred");
×
939
            ret = MENDER_FAIL;
×
940
            break;
×
941
        default:
×
942
            /* Should no occur */
943
            ret = MENDER_FAIL;
×
944
            break;
×
945
    }
946

947
    return ret;
333✔
948
}
949

950
void
951
mender_api_print_response_error(char *response, int status) {
2✔
952
    const char *desc;
953

954
    /* Treatment depending of the status */
955
    if (NULL != (desc = mender_utils_http_status_to_string(status))) {
2✔
956
        if (NULL != response) {
1✔
957
            cJSON *json_response = cJSON_Parse(response);
1✔
958
            if (NULL != json_response) {
1✔
959
                cJSON *json_error = cJSON_GetObjectItemCaseSensitive(json_response, "error");
1✔
960
                if (NULL != json_error) {
1✔
961
                    mender_log_error("[%d] %s: %s", status, desc, cJSON_GetStringValue(json_error));
1✔
962
                } else {
963
                    mender_log_error("[%d] %s: unknown error", status, desc);
×
964
                }
965
                cJSON_Delete(json_response);
1✔
966
            } else {
967
                mender_log_error("[%d] %s: unknown error", status, desc);
×
968
            }
969
        } else {
970
            mender_log_error("[%d] %s: unknown error", status, desc);
×
971
        }
972
    } else {
973
        mender_log_error("Unknown error occurred, status=%d", status);
1✔
974
    }
975
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc