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

mendersoftware / mender-mcu / 1898808013

30 Jun 2025 10:17PM UTC coverage: 57.26% (-0.1%) from 57.395%
1898808013

push

gitlab-ci

elkoniu
feat: Add backup root cert to Zephyr certs chain

For disaster recovery and emergency having single certificate is risky.
This change introduces 2nd root certificate to be used on the platform.

Changelog: Add backup root cert to the Zephyr certs chain
Ticket: MEN-8494
Signed-off-by: Paweł Poławski <pawel.polawski@northern.tech>

2295 of 4008 relevant lines covered (57.26%)

71.18 hits per line

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

61.62
/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 HTTP retry mechanism parameters
34
 *
35
 * On unreliable networks, an HTTP request can easily fail due to some short
36
 * network issue. Thus there needs to be a retry mechanism to prevent every
37
 * single such temporary failure to cause an error.
38
 *
39
 * 5 attempts (so 4 retries, to be precise) with 100ms as the first pause
40
 * interval doubled with every attempt gives a total of 1500ms period of retries
41
 * with nice progressive pauses in between (100, 200, 400, 800ms).
42
 *
43
 * @note: RETRY_ATTEMPTS is uint8_t,
44
 *        INTERVAL_BASE * INTERVAL_FACTOR**(ATTEMPTS - 1) has to fit in uint16_t
45
 *        Or the code below has to be adjusted, but such a long sleep doesn't make sense here!
46
 */
47
#define HTTP_RETRY_ATTEMPTS        5
48
#define HTTP_RETRY_INTERVAL_BASE   100 /* milliseconds */
49
#define HTTP_RETRY_INTERVAL_FACTOR 2
50

51
/**
52
 * @brief Paths of the mender-server APIs
53
 */
54
#define MENDER_API_PATH_POST_AUTHENTICATION_REQUESTS "/api/devices/v1/authentication/auth_requests"
55
#define MENDER_API_PATH_GET_NEXT_DEPLOYMENT          "/api/devices/v1/deployments/device/deployments/next"
56
#define MENDER_API_PATH_POST_NEXT_DEPLOYMENT_V2      "/api/devices/v2/deployments/device/deployments/next"
57
#define MENDER_API_PATH_PUT_DEPLOYMENT_STATUS        "/api/devices/v1/deployments/device/deployments/%s/status"
58
#define MENDER_API_PATH_PUT_DEPLOYMENT_LOGS          "/api/devices/v1/deployments/device/deployments/%s/log"
59
#define MENDER_API_PATH_GET_DEVICE_CONFIGURATION     "/api/devices/v1/deviceconfig/configuration"
60
#define MENDER_API_PATH_PUT_DEVICE_CONFIGURATION     "/api/devices/v1/deviceconfig/configuration"
61
#define MENDER_API_PATH_GET_DEVICE_CONNECT           "/api/devices/v1/deviceconnect/connect"
62
#define MENDER_API_PATH_PUT_DEVICE_ATTRIBUTES        "/api/devices/v1/inventory/device/attributes"
63

64
/**
65
 * @brief Mender API configuration
66
 */
67
static mender_api_config_t api_config;
68

69
/**
70
 * @brief Authentication token
71
 */
72
static char *api_jwt = NULL;
73

74
/**
75
 * @brief A mutex ensuring there are no concurrent operations using or updating the authentication token
76
 */
77
static void *auth_lock = NULL;
78

79
/**
80
 * @brief HTTP callback used to handle text content
81
 * @param event HTTP client event
82
 * @param data Data received
83
 * @param data_length Data length
84
 * @param params Callback parameters
85
 * @return MENDER_OK if the function succeeds, error code otherwise
86
 */
87
static mender_err_t mender_api_http_text_callback(mender_http_client_event_t event, void *data, size_t data_length, void *params);
88

89
/**
90
 * @brief Perform authentication of the device, retrieve token from mender-server used for the next requests
91
 * @return MENDER_OK if the function succeeds, error code otherwise
92
 */
93
static mender_err_t perform_authentication(void);
94

95
/**
96
 * @brief Ensure authenticated and holding the #auth_lock
97
 * @return MENDER_OK if success, MENDER_LOCK_FAILED in case of lock failure, other errors otherwise
98
 */
99
static mender_err_t ensure_authenticated_and_locked(void);
100

101
mender_err_t
102
mender_api_init(mender_api_config_t *config) {
29✔
103
    assert(NULL != config);
29✔
104
    assert(NULL != config->device_type);
29✔
105
    assert(NULL != config->host);
29✔
106
    assert(NULL != config->identity_cb);
29✔
107

108
    mender_err_t ret;
109

110
    /* Save configuration */
111
    memcpy(&api_config, config, sizeof(mender_api_config_t));
29✔
112

113
    /* Initializations */
114
    mender_http_config_t mender_http_config = { .host = api_config.host };
29✔
115
    if (MENDER_OK != (ret = mender_http_init(&mender_http_config))) {
29✔
116
        mender_log_error("Unable to initialize HTTP");
×
117
        return ret;
×
118
    }
119

120
    if (MENDER_OK != (ret = mender_os_mutex_create(&auth_lock))) {
29✔
121
        mender_log_error("Unable to initialize authentication lock");
×
122
        return ret;
×
123
    }
124

125
    return ret;
29✔
126
}
127

128
mender_err_t
129
mender_api_drop_authentication_data(void) {
6✔
130
    mender_err_t ret;
131
    if (MENDER_OK != (ret = mender_os_mutex_take(auth_lock, -1))) {
6✔
132
        mender_log_error("Unable to obtain the authentication lock");
×
133
        return MENDER_LOCK_FAILED;
×
134
    }
135
    FREE_AND_NULL(api_jwt);
6✔
136
    if (MENDER_OK != (ret = mender_os_mutex_give(auth_lock))) {
6✔
137
        mender_log_error("Unable to release the authentication lock");
×
138
    }
139

140
    return ret;
6✔
141
}
142

143
mender_err_t
144
mender_api_ensure_authenticated(void) {
6✔
145
    mender_err_t ret = ensure_authenticated_and_locked();
6✔
146
    if (MENDER_LOCK_FAILED == ret) {
5✔
147
        /* Error already logged. */
148
        return MENDER_FAIL;
×
149
    }
150
    bool authenticated = ((MENDER_OK == ret) || (MENDER_DONE == ret));
5✔
151

152
    if (MENDER_OK != (ret = mender_os_mutex_give(auth_lock))) {
5✔
153
        mender_log_error("Unable to release the authentication lock");
×
154
    }
155

156
    return authenticated ? ret : MENDER_FAIL;
5✔
157
}
158

159
static mender_err_t
160
ensure_authenticated_and_locked(void) {
127✔
161
    mender_err_t ret;
162

163
    if (MENDER_OK != (ret = mender_os_mutex_take(auth_lock, -1))) {
127✔
164
        mender_log_error("Unable to obtain the authentication lock");
×
165
        return MENDER_LOCK_FAILED;
×
166
    }
167

168
    if (NULL != api_jwt) {
127✔
169
        return MENDER_DONE;
97✔
170
    }
171

172
    /* Perform authentication with the mender server */
173
    if (MENDER_OK != (ret = perform_authentication())) {
30✔
174
        mender_log_error("Authentication failed");
×
175
        return ret;
×
176
    } else {
177
        mender_log_debug("Authenticated successfully");
29✔
178
    }
179

180
    return ret;
29✔
181
}
182

183
static mender_err_t
184
perform_authentication(void) {
30✔
185
    mender_err_t             ret;
186
    char                    *public_key_pem   = NULL;
30✔
187
    const mender_identity_t *identity         = NULL;
30✔
188
    cJSON                   *json_identity    = NULL;
30✔
189
    char                    *identity_info    = NULL;
30✔
190
    cJSON                   *json_payload     = NULL;
30✔
191
    char                    *payload          = NULL;
30✔
192
    char                    *response         = NULL;
30✔
193
    char                    *signature        = NULL;
30✔
194
    size_t                   signature_length = 0;
30✔
195
    int                      status           = 0;
30✔
196
    uint8_t                  remaining_attempts;
197
    uint16_t                 retry_interval;
198

199
    /* Get public key in PEM format */
200
    if (MENDER_OK != (ret = mender_tls_get_public_key_pem(&public_key_pem))) {
30✔
201
        mender_log_error("Unable to get public key");
×
202
        goto END;
×
203
    }
204

205
    /* Get identity (we don't own the returned data) */
206
    if (MENDER_OK != (ret = api_config.identity_cb(&identity))) {
30✔
207
        mender_log_error("Unable to get identity");
×
208
        goto END;
×
209
    }
210

211
    /* Format identity */
212
    if (MENDER_OK != (ret = mender_utils_identity_to_json(identity, &json_identity))) {
30✔
213
        mender_log_error("Unable to format identity");
×
214
        goto END;
×
215
    }
216
    if (NULL == (identity_info = cJSON_PrintUnformatted(json_identity))) {
30✔
217
        mender_log_error("Unable to allocate memory");
×
218
        ret = MENDER_FAIL;
×
219
        goto END;
×
220
    }
221

222
    /* Format payload */
223
    if (NULL == (json_payload = cJSON_CreateObject())) {
30✔
224
        mender_log_error("Unable to allocate memory");
×
225
        ret = MENDER_FAIL;
×
226
        goto END;
×
227
    }
228
    cJSON_AddStringToObject(json_payload, "id_data", identity_info);
30✔
229
    cJSON_AddStringToObject(json_payload, "pubkey", public_key_pem);
30✔
230
    if (NULL != api_config.tenant_token) {
30✔
231
        cJSON_AddStringToObject(json_payload, "tenant_token", api_config.tenant_token);
30✔
232
    }
233
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
30✔
234
        mender_log_error("Unable to allocate memory");
×
235
        ret = MENDER_FAIL;
×
236
        goto END;
×
237
    }
238

239
    /* Sign payload */
240
    if (MENDER_OK != (ret = mender_tls_sign_payload(payload, &signature, &signature_length))) {
30✔
241
        mender_log_error("Unable to sign payload");
×
242
        goto END;
×
243
    }
244

245
    /* Perform HTTP request */
246
    remaining_attempts = HTTP_RETRY_ATTEMPTS;
30✔
247
    retry_interval     = HTTP_RETRY_INTERVAL_BASE;
30✔
248
    do {
249
        ret = mender_http_perform(NULL,
38✔
250
                                  MENDER_API_PATH_POST_AUTHENTICATION_REQUESTS,
251
                                  MENDER_HTTP_POST,
252
                                  payload,
253
                                  signature,
254
                                  &mender_api_http_text_callback,
255
                                  (void *)&response,
256
                                  &status);
257
        if (MENDER_RETRY_ERROR == ret) {
37✔
258
            mender_os_sleep(retry_interval);
8✔
259
            retry_interval = retry_interval * HTTP_RETRY_INTERVAL_FACTOR;
8✔
260
            remaining_attempts--;
8✔
261

262
            /* Just in case something was already gathered as response. */
263
            FREE_AND_NULL(response);
8✔
264
        }
265
    } while ((MENDER_RETRY_ERROR == ret) && (remaining_attempts > 0));
37✔
266

267
    if (MENDER_OK != ret) {
29✔
268
        mender_log_error("Unable to perform HTTP request");
×
269
        mender_err_count_net_inc();
×
270
        goto END;
×
271
    }
272

273
    /* Treatment depending of the status */
274
    if (200 == status) {
29✔
275
        if (NULL == response) {
29✔
276
            mender_log_error("Response is empty");
×
277
            ret = MENDER_FAIL;
×
278
            goto END;
×
279
        }
280
        if (NULL != api_jwt) {
29✔
281
            mender_free(api_jwt);
×
282
        }
283
        if (NULL == (api_jwt = mender_utils_strdup(response))) {
29✔
284
            mender_log_error("Unable to allocate memory");
×
285
            ret = MENDER_FAIL;
×
286
            goto END;
×
287
        }
288
        ret = MENDER_OK;
29✔
289
    } else {
290
        mender_api_print_response_error(response, status);
×
291
        /* Maybe the identity is wrong? Let's make sure we get fresh data for the next attempt. */
292
        FREE_AND_NULL(identity_info);
×
293
        ret = MENDER_RETRY_ERROR;
×
294
    }
295

296
END:
29✔
297

298
    /* Release memory */
299
    mender_free(response);
29✔
300
    mender_free(signature);
29✔
301
    mender_free(payload);
29✔
302
    cJSON_Delete(json_payload);
29✔
303
    cJSON_Delete(json_identity);
29✔
304
    mender_free(identity_info);
29✔
305
    mender_free(public_key_pem);
29✔
306

307
    return ret;
29✔
308
}
309

310
/**
311
 * @see mender_http_perform()
312
 */
313
static mender_err_t
314
authenticated_http_perform(char *path, mender_http_method_t method, char *payload, char *signature, char **response, int *status) {
121✔
315
    mender_err_t ret;
316
    uint8_t      remaining_attempts;
317
    uint16_t     retry_interval;
318

319
    if (MENDER_IS_ERROR(ret = ensure_authenticated_and_locked())) {
121✔
320
        /* Errors already logged. */
321
        if (MENDER_LOCK_FAILED != ret) {
×
322
            if (MENDER_OK != mender_os_mutex_give(auth_lock)) {
×
323
                mender_log_error("Unable to release the authentication lock");
×
324
                return MENDER_FAIL;
×
325
            }
326
        }
327
        return ret;
×
328
    }
329

330
    remaining_attempts = HTTP_RETRY_ATTEMPTS;
121✔
331
    retry_interval     = HTTP_RETRY_INTERVAL_BASE;
121✔
332
    do {
333
        ret = mender_http_perform(api_jwt, path, method, payload, signature, &mender_api_http_text_callback, response, status);
121✔
334
        if (MENDER_RETRY_ERROR == ret) {
107✔
335
            mender_os_sleep(retry_interval);
×
336
            retry_interval = retry_interval * HTTP_RETRY_INTERVAL_FACTOR;
×
337
            remaining_attempts--;
×
338

339
            /* Just in case something was already gathered as response. */
340
            FREE_AND_NULL(*response);
×
341
        }
342
    } while ((MENDER_RETRY_ERROR == ret) && (remaining_attempts > 0));
107✔
343

344
    if (MENDER_OK != mender_os_mutex_give(auth_lock)) {
107✔
345
        mender_log_error("Unable to release the authentication lock");
×
346
        return MENDER_FAIL;
×
347
    }
348
    if (MENDER_OK != ret) {
107✔
349
        /* HTTP errors already logged. */
350
        mender_err_count_net_inc();
×
351
        return ret;
×
352
    }
353

354
    if (401 == *status) {
107✔
355
        /* Unauthorized => try to re-authenticate and perform the request again */
356
        mender_log_info("Trying to re-authenticate");
×
357
        FREE_AND_NULL(api_jwt);
×
358
        if (MENDER_IS_ERROR(ret = ensure_authenticated_and_locked())) {
×
359
            mender_free(*response);
×
360
            ret = mender_http_perform(api_jwt, path, method, payload, signature, &mender_api_http_text_callback, response, status);
×
361
            if (MENDER_OK != mender_os_mutex_give(auth_lock)) {
×
362
                mender_log_error("Unable to release the authentication lock");
×
363
                return MENDER_FAIL;
×
364
            }
365
            if (MENDER_OK != ret) {
×
366
                /* HTTP errors already logged. */
367
                mender_err_count_net_inc();
×
368
            }
369
        } else if (MENDER_LOCK_FAILED != ret) {
×
370
            if (MENDER_OK != mender_os_mutex_give(auth_lock)) {
×
371
                mender_log_error("Unable to release the authentication lock");
×
372
                return MENDER_FAIL;
×
373
            }
374
        }
375
    }
376

377
    return ret;
107✔
378
}
379

380
static mender_err_t
381
api_check_for_deployment_v2(int *status, char **response) {
39✔
382
    assert(NULL != status);
39✔
383
    assert(NULL != response);
39✔
384

385
    mender_err_t ret           = MENDER_FAIL;
39✔
386
    cJSON       *json_payload  = NULL;
39✔
387
    char        *payload       = NULL;
39✔
388
    const char  *artifact_name = NULL;
39✔
389
#ifdef CONFIG_MENDER_PROVIDES_DEPENDS
390
#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT
391
    mender_key_value_list_t *provides = NULL;
39✔
392
#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */
393
#endif /* CONFIG_MENDER_PROVIDES_DEPENDS */
394

395
    /* Create payload */
396
    if (NULL == (json_payload = cJSON_CreateObject())) {
39✔
397
        mender_log_error("Unable to allocate memory");
×
398
        goto END;
×
399
    }
400

401
    /* Add "device_provides" entity to payload */
402
    cJSON *json_provides = NULL;
39✔
403
    if (NULL == (json_provides = cJSON_AddObjectToObject(json_payload, "device_provides"))) {
39✔
404
        mender_log_error("Unable to allocate memory");
×
405
        goto END;
×
406
    }
407

408
    if (NULL == cJSON_AddStringToObject(json_provides, "device_type", api_config.device_type)) {
39✔
409
        mender_log_error("Unable to allocate memory");
×
410
        goto END;
×
411
    }
412

413
#ifdef CONFIG_MENDER_PROVIDES_DEPENDS
414
#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT
415
    /* Add provides from storage */
416
    if (MENDER_FAIL == mender_storage_get_provides(&provides)) {
39✔
417
        mender_log_error("Unable to get provides");
×
418
        goto END;
×
419
    }
420
    for (mender_key_value_list_t *item = provides; NULL != item; item = item->next) {
44✔
421
        if (NULL == cJSON_AddStringToObject(json_provides, item->key, item->value)) {
5✔
422
            mender_log_error("Unable to allocate memory");
×
423
            goto END;
×
424
        }
425
    }
426
#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */
427
#endif /* CONFIG_MENDER_PROVIDES_DEPENDS */
428

429
    if ((MENDER_OK != mender_storage_get_artifact_name(&artifact_name)) && (NULL != artifact_name)) {
39✔
430
        mender_log_error("Unable to get artifact name");
×
431
        return MENDER_FAIL;
×
432
    }
433

434
    if (NULL == cJSON_AddStringToObject(json_provides, "artifact_name", artifact_name)) {
39✔
435
        mender_log_error("Unable to allocate memory");
×
436
        goto END;
×
437
    }
438

439
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
39✔
440
        mender_log_error("Unable to allocate memory");
×
441
        goto END;
×
442
    }
443

444
    /* Perform HTTP request */
445
    if (MENDER_OK != (ret = authenticated_http_perform(MENDER_API_PATH_POST_NEXT_DEPLOYMENT_V2, MENDER_HTTP_POST, payload, NULL, response, status))) {
39✔
446
        mender_log_error("Unable to perform HTTP request");
×
447
        goto END;
×
448
    }
449

450
    ret = MENDER_OK;
27✔
451

452
END:
27✔
453

454
#ifdef CONFIG_MENDER_PROVIDES_DEPENDS
455
#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT
456
    mender_utils_key_value_list_free(provides);
27✔
457
#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */
458
#endif /* CONFIG_MENDER_PROVIDES_DEPENDS */
459
    cJSON_Delete(json_payload);
27✔
460
    mender_free(payload);
27✔
461
    return ret;
27✔
462
}
463

464
static mender_err_t
465
api_check_for_deployment_v1(int *status, char **response) {
×
466

467
    assert(NULL != status);
×
468
    assert(NULL != response);
×
469

470
    mender_err_t ret           = MENDER_FAIL;
×
471
    char        *path          = NULL;
×
472
    const char  *artifact_name = NULL;
×
473

474
    if ((MENDER_OK != mender_storage_get_artifact_name(&artifact_name)) && (NULL != artifact_name)) {
×
475
        mender_log_error("Unable to get artifact name");
×
476
        return MENDER_FAIL;
×
477
    }
478

479
    /* Compute path */
480
    if (-1 == mender_utils_asprintf(&path, MENDER_API_PATH_GET_NEXT_DEPLOYMENT "?artifact_name=%s&device_type=%s", artifact_name, api_config.device_type)) {
×
481
        mender_log_error("Unable to allocate memory");
×
482
        goto END;
×
483
    }
484

485
    /* Perform HTTP request */
486
    if (MENDER_OK != (ret = authenticated_http_perform(path, MENDER_HTTP_GET, NULL, NULL, response, status))) {
×
487
        mender_log_error("Unable to perform HTTP request");
×
488
        goto END;
×
489
    }
490

491
    ret = MENDER_OK;
×
492

493
END:
×
494

495
    /* Release memory */
496
    mender_free(path);
×
497

498
    return ret;
×
499
}
500

501
mender_err_t
502
mender_api_check_for_deployment(mender_api_deployment_data_t *deployment) {
39✔
503

504
    assert(NULL != deployment);
39✔
505
    mender_err_t ret      = MENDER_FAIL;
39✔
506
    char        *response = NULL;
39✔
507
    int          status   = 0;
39✔
508

509
    if (MENDER_FAIL == (ret = api_check_for_deployment_v2(&status, &response))) {
39✔
510
        goto END;
×
511
    }
512

513
    /* Yes, 404 still means MENDER_OK above */
514
    if (404 == status) {
27✔
515
        mender_log_debug("POST request to v2 version of the deployments API failed, falling back to v1 version and GET");
516
        FREE_AND_NULL(response);
×
517
        if (MENDER_FAIL == (ret = api_check_for_deployment_v1(&status, &response))) {
×
518
            goto END;
×
519
        }
520
    }
521

522
    /* Treatment depending of the status */
523
    if (200 == status) {
27✔
524
        cJSON *json_response = cJSON_Parse(response);
12✔
525
        if (NULL != json_response) {
12✔
526
            cJSON *json_id = cJSON_GetObjectItem(json_response, "id");
12✔
527
            if (NULL != json_id) {
12✔
528
                if (NULL == (deployment->id = mender_utils_strdup(cJSON_GetStringValue(json_id)))) {
12✔
529
                    ret = MENDER_FAIL;
×
530
                    goto END;
×
531
                }
532
            }
533
            cJSON *json_artifact = cJSON_GetObjectItem(json_response, "artifact");
12✔
534
            if (NULL != json_artifact) {
12✔
535
                cJSON *json_artifact_name = cJSON_GetObjectItem(json_artifact, "artifact_name");
12✔
536
                if (NULL != json_artifact_name) {
12✔
537
                    if (NULL == (deployment->artifact_name = mender_utils_strdup(cJSON_GetStringValue(json_artifact_name)))) {
12✔
538
                        ret = MENDER_FAIL;
×
539
                        goto END;
×
540
                    }
541
                }
542
                cJSON *json_source = cJSON_GetObjectItem(json_artifact, "source");
12✔
543
                if (NULL != json_source) {
12✔
544
                    cJSON *json_uri = cJSON_GetObjectItem(json_source, "uri");
12✔
545
                    if (NULL != json_uri) {
12✔
546
                        if (NULL == (deployment->uri = mender_utils_strdup(cJSON_GetStringValue(json_uri)))) {
12✔
547
                            ret = MENDER_FAIL;
×
548
                            goto END;
×
549
                        }
550
                        ret = MENDER_OK;
12✔
551
                    } else {
552
                        mender_log_error("Invalid response");
×
553
                        ret = MENDER_FAIL;
×
554
                    }
555
                } else {
556
                    mender_log_error("Invalid response");
×
557
                    ret = MENDER_FAIL;
×
558
                }
559
                cJSON *json_device_types_compatible = cJSON_GetObjectItem(json_artifact, "device_types_compatible");
12✔
560
                if (NULL != json_device_types_compatible && cJSON_IsArray(json_device_types_compatible)) {
12✔
561
                    deployment->device_types_compatible_size = cJSON_GetArraySize(json_device_types_compatible);
12✔
562
                    deployment->device_types_compatible      = (char **)mender_malloc(deployment->device_types_compatible_size * sizeof(char *));
12✔
563
                    if (NULL == deployment->device_types_compatible) {
12✔
564
                        mender_log_error("Unable to allocate memory");
×
565
                        ret = MENDER_FAIL;
×
566
                        goto END;
×
567
                    }
568
                    for (size_t i = 0; i < deployment->device_types_compatible_size; i++) {
24✔
569
                        cJSON *json_device_type = cJSON_GetArrayItem(json_device_types_compatible, i);
12✔
570
                        if (NULL != json_device_type && cJSON_IsString(json_device_type)) {
12✔
571
                            if (NULL == (deployment->device_types_compatible[i] = mender_utils_strdup(cJSON_GetStringValue(json_device_type)))) {
12✔
572
                                ret = MENDER_FAIL;
×
573
                                goto END;
×
574
                            }
575
                        } else {
576
                            mender_log_error("Could not get device type form device_types_compatible array");
×
577
                            ret = MENDER_FAIL;
×
578
                        }
579
                    }
580
                } else {
581
                    mender_log_error("Could not load device_types_compatible");
×
582
                    ret = MENDER_FAIL;
×
583
                }
584
            } else {
585
                mender_log_error("Invalid response");
×
586
                ret = MENDER_FAIL;
×
587
            }
588
            cJSON_Delete(json_response);
12✔
589
        } else {
590
            mender_log_error("Invalid response");
×
591
            ret = MENDER_FAIL;
×
592
        }
593
    } else if (204 == status) {
15✔
594
        /* No response expected */
595
        ret = MENDER_NOT_FOUND;
15✔
596
    } else {
597
        mender_api_print_response_error(response, status);
×
598
        ret = MENDER_RETRY_ERROR;
×
599
    }
600

601
END:
27✔
602

603
    /* Release memory */
604
    mender_free(response);
27✔
605

606
    return ret;
27✔
607
}
608

609
#ifdef CONFIG_MENDER_DEPLOYMENT_LOGS
610
static mender_err_t mender_api_publish_deployment_logs(const char *id);
611
#endif /* CONFIG_MENDER_DEPLOYMENT_LOGS */
612

613
mender_err_t
614
mender_api_publish_deployment_status(const char *id, mender_deployment_status_t deployment_status) {
47✔
615
    assert(NULL != id);
47✔
616

617
    mender_err_t ret;
618
    const char  *value        = NULL;
47✔
619
    cJSON       *json_payload = NULL;
47✔
620
    char        *payload      = NULL;
47✔
621
    char        *path         = NULL;
47✔
622
    char        *response     = NULL;
47✔
623
    int          status       = 0;
47✔
624

625
    /* Deployment status to string */
626
    if (NULL == (value = mender_utils_deployment_status_to_string(deployment_status))) {
47✔
627
        mender_log_error("Invalid status");
×
628
        ret = MENDER_FAIL;
×
629
        goto END;
×
630
    }
631

632
    /* Format payload */
633
    if (NULL == (json_payload = cJSON_CreateObject())) {
47✔
634
        mender_log_error("Unable to allocate memory");
×
635
        ret = MENDER_FAIL;
×
636
        goto END;
×
637
    }
638
    cJSON_AddStringToObject(json_payload, "status", value);
47✔
639
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
47✔
640
        mender_log_error("Unable to allocate memory");
×
641
        ret = MENDER_FAIL;
×
642
        goto END;
×
643
    }
644

645
    /* Compute path */
646
    if (mender_utils_asprintf(&path, MENDER_API_PATH_PUT_DEPLOYMENT_STATUS, id) <= 0) {
47✔
647
        mender_log_error("Unable to allocate memory");
×
648
        ret = MENDER_FAIL;
×
649
        goto END;
×
650
    }
651

652
    /* Perform HTTP request */
653
    if (MENDER_OK != (ret = authenticated_http_perform(path, MENDER_HTTP_PUT, payload, NULL, &response, &status))) {
47✔
654
        mender_log_error("Unable to perform HTTP request");
×
655
        goto END;
×
656
    }
657

658
    /* Treatment depending of the status */
659
    if (204 == status) {
45✔
660
        /* No response expected */
661
        ret = MENDER_OK;
44✔
662
    } else if (409 == status) {
1✔
663
        /* Deployment aborted */
664
        mender_api_print_response_error(response, status);
1✔
665
        ret = MENDER_ABORTED;
1✔
666
    } else {
667
        mender_api_print_response_error(response, status);
×
668
        ret = MENDER_RETRY_ERROR;
×
669
    }
670

671
END:
45✔
672

673
    /* Release memory */
674
    mender_free(response);
45✔
675
    mender_free(path);
45✔
676
    mender_free(payload);
45✔
677
    cJSON_Delete(json_payload);
45✔
678

679
#ifdef CONFIG_MENDER_DEPLOYMENT_LOGS
680
    /* Do this after we have released memory above, potentially giving us some
681
       extra room we may need. */
682
    if ((MENDER_OK == ret) && (MENDER_DEPLOYMENT_STATUS_FAILURE == deployment_status)) {
45✔
683
        /* Successfully reported a deployment failure, upload deployment
684
           logs.  */
685
        if (MENDER_OK != mender_api_publish_deployment_logs(id)) {
9✔
686
            mender_log_error("Failed to publish deployment logs");
687
        }
688
    }
689
#endif /* CONFIG_MENDER_DEPLOYMENT_LOGS */
690

691
    return ret;
45✔
692
}
693

694
#ifdef CONFIG_MENDER_DEPLOYMENT_LOGS
695
static void
696
append_depl_log_msg(char *msg, void *ctx) {
25✔
697
    assert(NULL != ctx);
25✔
698

699
    char  *tstamp   = NULL;
25✔
700
    char  *level    = NULL;
25✔
701
    char  *log_msg  = NULL;
25✔
702
    cJSON *json_msg = NULL;
25✔
703
    cJSON *messages = ctx;
25✔
704

705
    /* Example log message we expect:
706
     *   "[00:39:06.746,000] <err> mender: Unable to perform HTTP request"
707
     * The code below goes through the string, searches for the expected parts and breaks the string
708
     * down accordingly.
709
     */
710

711
    /* Start by setting log_msg to the whole message. In case all of the
712
       break-down below fails, we send the whole message as the log message with
713
       no extra metadata. */
714
    log_msg = msg;
25✔
715

716
    char *c = msg;
25✔
717
    if ('[' == *c) {
25✔
718
        /* if it does start with a timestamp, like above, store the pointer and find its end */
719
        c++;
25✔
720
        tstamp = c;
25✔
721
        while (('\0' != *c) && (']' != *c)) {
700✔
722
            c++;
675✔
723
        }
724
        if ('\0' == *c) {
25✔
725
            goto DONE_PARSING;
726
        }
727
        *c = '\0';
25✔
728
        c++;
25✔
729
    }
730

731
    if (' ' == *c) {
25✔
732
        /* skip the space */
733
        c++;
25✔
734
    }
735

736
    if ('<' == *c) {
25✔
737
        /* if the log level follow, like above, store the pointer and find its end */
738
        c++;
25✔
739
        level = c;
25✔
740
        while (('\0' != *c) && ('>' != *c)) {
100✔
741
            c++;
75✔
742
        }
743
        if ('\0' == *c) {
25✔
744
            goto DONE_PARSING;
745
        }
746
        *c = '\0';
25✔
747
        c++;
25✔
748
    }
749

750
    if (' ' == *c) {
25✔
751
        /* skip the space */
752
        c++;
25✔
753
    }
754

755
    if ('\0' != *c) {
25✔
756
        log_msg = c;
25✔
757
        if (mender_utils_strbeginswith(log_msg, "mender: ")) {
25✔
758
            log_msg += strlen("mender: ");
25✔
759
        }
760
    }
761

762
DONE_PARSING:
763
    if (NULL == (json_msg = cJSON_CreateObject())) {
25✔
764
        mender_log_error("Unable to allocate memory");
765
        return;
766
    }
767

768
    if (NULL != tstamp) {
25✔
769
        if (NULL == cJSON_AddStringToObject(json_msg, "timestamp", tstamp)) {
25✔
770
            mender_log_error("Unable to allocate memory");
771
            goto END;
772
        }
773
    } else {
774
        if (NULL == cJSON_AddNullToObject(json_msg, "timestamp")) {
775
            mender_log_error("Unable to allocate memory");
776
            goto END;
777
        }
778
    }
779

780
    if (NULL != level) {
25✔
781
        if (NULL == cJSON_AddStringToObject(json_msg, "level", level)) {
25✔
782
            mender_log_error("Unable to allocate memory");
783
            goto END;
784
        }
785
    } else {
786
        if (NULL == cJSON_AddNullToObject(json_msg, "level")) {
787
            mender_log_error("Unable to allocate memory");
788
            goto END;
789
        }
790
    }
791

792
    if (NULL == cJSON_AddStringToObject(json_msg, "message", log_msg)) {
25✔
793
        mender_log_error("Unable to allocate memory");
794
        goto END;
795
    }
796

797
    if (!cJSON_AddItemToArray(messages, json_msg)) {
25✔
798
        mender_log_error("Unable to allocate memory");
799
    }
800
    json_msg = NULL;
25✔
801

802
END:
25✔
803
    cJSON_Delete(json_msg);
25✔
804
}
805

806
static mender_err_t
807
mender_api_publish_deployment_logs(const char *id) {
9✔
808
    assert(NULL != id);
9✔
809

810
    mender_err_t ret;
811
    cJSON       *json_payload  = NULL;
9✔
812
    cJSON       *json_messages = NULL;
9✔
813
    char        *payload       = NULL;
9✔
814
    char        *path          = NULL;
9✔
815
    char        *response      = NULL;
9✔
816
    int          status        = 0;
9✔
817

818
    /* Format payload */
819
    if (NULL == (json_payload = cJSON_CreateObject())) {
9✔
820
        mender_log_error("Unable to allocate memory");
821
        ret = MENDER_FAIL;
822
        goto END;
823
    }
824
    if (NULL == (json_messages = cJSON_AddArrayToObject(json_payload, "messages"))) {
9✔
825
        mender_log_error("Unable to allocate memory");
826
        ret = MENDER_FAIL;
827
        goto END;
828
    }
829

830
    if (MENDER_OK != (ret = mender_storage_deployment_log_walk(append_depl_log_msg, json_messages))) {
9✔
831
        mender_log_error("Failed to add deployment log messages to payload");
832
        ret = MENDER_FAIL;
833
        goto END;
834
    }
835

836
    if (0 == cJSON_GetArraySize(json_messages)) {
9✔
837
        /* Nothing to do, no logs to submit. */
838
        ret = MENDER_OK;
3✔
839
        goto END;
3✔
840
    }
841

842
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
6✔
843
        mender_log_error("Unable to allocate memory");
844
        ret = MENDER_FAIL;
845
        goto END;
846
    }
847
    /* We no longer need the JSON now that we have the string representation so
848
       reclaim that (potentially big) chunk of memory). */
849
    DESTROY_AND_NULL(cJSON_Delete, json_payload);
6✔
850

851
    /* Perform HTTP request */
852
    if (mender_utils_asprintf(&path, MENDER_API_PATH_PUT_DEPLOYMENT_LOGS, id) <= 0) {
6✔
853
        mender_log_error("Unable to allocate memory");
854
        goto END;
855
    }
856

857
    mender_log_info("Publishing deployment logs");
6✔
858
    if (MENDER_OK != (ret = authenticated_http_perform(path, MENDER_HTTP_PUT, payload, NULL, &response, &status))) {
6✔
859
        mender_log_error("Unable to perform HTTP request");
860
        goto END;
861
    }
862

863
    /* Treatment depending of the status */
864
    if (204 == status) {
6✔
865
        /* No response expected */
866
        ret = MENDER_OK;
6✔
867
    } else if (409 == status) {
868
        /* Deployment aborted */
869
        mender_api_print_response_error(response, status);
870
        ret = MENDER_ABORTED;
871
    } else {
872
        mender_api_print_response_error(response, status);
873
        ret = MENDER_FAIL;
874
    }
875

876
END:
9✔
877

878
    /* Release memory */
879
    mender_free(response);
9✔
880
    mender_free(path);
9✔
881
    mender_free(payload);
9✔
882
    cJSON_Delete(json_payload);
9✔
883

884
    return ret;
9✔
885
}
886
#endif /* CONFIG_MENDER_DEPLOYMENT_LOGS */
887

888
#ifndef CONFIG_MENDER_CLIENT_INVENTORY_DISABLE
889

890
mender_err_t
891
mender_api_publish_inventory_data(cJSON *inventory, bool patch) {
29✔
892

893
    mender_err_t ret;
894
    char        *payload  = NULL;
29✔
895
    char        *response = NULL;
29✔
896
    int          status   = 0;
29✔
897

898
    /* Format payload */
899
    if (NULL == (payload = cJSON_PrintUnformatted(inventory))) {
29✔
900
        mender_log_error("Unable to allocate memory");
×
901
        ret = MENDER_FAIL;
×
902
        goto END;
×
903
    }
904

905
    /* Perform HTTP request */
906
    if (MENDER_OK
29✔
907
        != (ret = authenticated_http_perform(
29✔
908
                MENDER_API_PATH_PUT_DEVICE_ATTRIBUTES, patch ? MENDER_HTTP_PATCH : MENDER_HTTP_PUT, payload, NULL, &response, &status))) {
909
        mender_log_error("Unable to perform HTTP request");
×
910
        goto END;
×
911
    }
912

913
    /* Treatment depending of the status */
914
    if (200 == status) {
29✔
915
        /* No response expected */
916
        ret = MENDER_OK;
29✔
917
    } else {
918
        mender_api_print_response_error(response, status);
×
919
        ret = MENDER_RETRY_ERROR;
×
920
    }
921

922
END:
29✔
923

924
    /* Release memory */
925
    mender_free(response);
29✔
926
    mender_free(payload);
29✔
927
    cJSON_Delete(inventory);
29✔
928

929
    return ret;
29✔
930
}
931

932
#endif /* CONFIG_MENDER_CLIENT_INVENTORY_DISABLE */
933

934
mender_err_t
935
mender_api_exit(void) {
×
936

937
    /* Release all modules */
938
    mender_http_exit();
×
939

940
    /* Destroy the authentication lock */
941
    mender_os_mutex_delete(auth_lock);
×
942

943
    /* Release memory */
944
    FREE_AND_NULL(api_jwt);
×
945

946
    return MENDER_OK;
×
947
}
948

949
static mender_err_t
950
mender_api_http_text_callback(mender_http_client_event_t event, void *data, size_t data_length, void *params) {
367✔
951

952
    assert(NULL != params);
367✔
953
    char       **response = (char **)params;
367✔
954
    mender_err_t ret      = MENDER_OK;
367✔
955
    char        *tmp;
956

957
    /* Treatment depending of the event */
958
    switch (event) {
367✔
959
        case MENDER_HTTP_EVENT_CONNECTED:
136✔
960
            /* Nothing to do */
961
            break;
136✔
962
        case MENDER_HTTP_EVENT_DATA_RECEIVED:
95✔
963
            /* Check input data */
964
            if ((NULL == data) || (0 == data_length)) {
95✔
965
                mender_log_error("Invalid data received");
×
966
                ret = MENDER_FAIL;
×
967
                break;
×
968
            }
969
            /* Concatenate data to the response */
970
            size_t response_length = (NULL != *response) ? strlen(*response) : 0;
95✔
971
            if (NULL == (tmp = mender_realloc(*response, response_length + data_length + 1))) {
95✔
972
                mender_log_error("Unable to allocate memory");
×
973
                ret = MENDER_FAIL;
×
974
                break;
×
975
            }
976
            *response = tmp;
95✔
977
            memcpy((*response) + response_length, data, data_length);
95✔
978
            *((*response) + response_length + data_length) = '\0';
95✔
979
            break;
95✔
980
        case MENDER_HTTP_EVENT_DISCONNECTED:
136✔
981
            /* Nothing to do */
982
            break;
136✔
983
        case MENDER_HTTP_EVENT_ERROR:
×
984
            /* Downloading the response fails */
985
            mender_log_error("An error occurred");
×
986
            ret = MENDER_FAIL;
×
987
            break;
×
988
        default:
×
989
            /* Should no occur */
990
            ret = MENDER_FAIL;
×
991
            break;
×
992
    }
993

994
    return ret;
367✔
995
}
996

997
void
998
mender_api_print_response_error(char *response, int status) {
2✔
999
    const char *desc;
1000

1001
    /* Treatment depending of the status */
1002
    if (NULL != (desc = mender_utils_http_status_to_string(status))) {
2✔
1003
        if (NULL != response) {
1✔
1004
            cJSON *json_response = cJSON_Parse(response);
1✔
1005
            if (NULL != json_response) {
1✔
1006
                cJSON *json_error = cJSON_GetObjectItemCaseSensitive(json_response, "error");
1✔
1007
                if (NULL != json_error) {
1✔
1008
                    mender_log_error("[%d] %s: %s", status, desc, cJSON_GetStringValue(json_error));
1✔
1009
                } else {
1010
                    mender_log_error("[%d] %s: unknown error", status, desc);
×
1011
                }
1012
                cJSON_Delete(json_response);
1✔
1013
            } else {
1014
                mender_log_error("[%d] %s: unknown error", status, desc);
×
1015
            }
1016
        } else {
1017
            mender_log_error("[%d] %s: unknown error", status, desc);
×
1018
        }
1019
    } else {
1020
        mender_log_error("Unknown error occurred, status=%d", status);
1✔
1021
    }
1022
}
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